Skip to main content

src/etui@widgets@notification.erl

-module(etui@widgets@notification).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/widgets/notification.gleam").
-export([queue_new/1, with_corner/2, info/2, success/2, warning/2, error/2, persistent/2, push/2, tick/1, dismiss_level/2, dismiss_all/1, dismiss_first/1, has_notifications/1, count/1, render/3]).
-export_type([level/0, notification/0, notification_queue/0, corner/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

-type level() :: info | success | warning | error.

-type notification() :: {notification, binary(), level(), integer()}.

-type notification_queue() :: {notification_queue,
        list(notification()),
        integer(),
        corner()}.

-type corner() :: top_right | top_left | bottom_right | bottom_left.

-file("src/etui/widgets/notification.gleam", 72).
?DOC(" New empty queue. Max = 5, corner = BottomRight.\n").
-spec queue_new(integer()) -> notification_queue().
queue_new(Max) ->
    {notification_queue, [], Max, bottom_right}.

-file("src/etui/widgets/notification.gleam", 77).
?DOC(" Set which corner to stack notifications.\n").
-spec with_corner(notification_queue(), corner()) -> notification_queue().
with_corner(Q, Corner) ->
    {notification_queue, erlang:element(2, Q), erlang:element(3, Q), Corner}.

-file("src/etui/widgets/notification.gleam", 82).
?DOC(" Build an Info notification.\n").
-spec info(binary(), integer()) -> notification().
info(Message, Ttl) ->
    {notification, Message, info, Ttl}.

-file("src/etui/widgets/notification.gleam", 87).
?DOC(" Build a Success notification.\n").
-spec success(binary(), integer()) -> notification().
success(Message, Ttl) ->
    {notification, Message, success, Ttl}.

-file("src/etui/widgets/notification.gleam", 92).
?DOC(" Build a Warning notification.\n").
-spec warning(binary(), integer()) -> notification().
warning(Message, Ttl) ->
    {notification, Message, warning, Ttl}.

-file("src/etui/widgets/notification.gleam", 97).
?DOC(" Build an Error notification (persistent by default: ttl = -1).\n").
-spec error(binary(), integer()) -> notification().
error(Message, Ttl) ->
    {notification, Message, error, Ttl}.

-file("src/etui/widgets/notification.gleam", 102).
?DOC(" Persistent notification (never auto-expires; dismiss manually).\n").
-spec persistent(binary(), level()) -> notification().
persistent(Message, Level) ->
    {notification, Message, Level, -1}.

-file("src/etui/widgets/notification.gleam", 110).
?DOC(" Add a notification. If queue is full, the oldest is dropped.\n").
-spec push(notification_queue(), notification()) -> notification_queue().
push(Q, N) ->
    Items = lists:append(erlang:element(2, Q), [N]),
    Trimmed = case erlang:length(Items) > erlang:element(3, Q) of
        true ->
            gleam@list:drop(Items, erlang:length(Items) - erlang:element(3, Q));

        false ->
            Items
    end,
    {notification_queue, Trimmed, erlang:element(3, Q), erlang:element(4, Q)}.

-file("src/etui/widgets/notification.gleam", 121).
?DOC(
    " Advance time by one tick. Decrements TTL on all non-persistent\n"
    " notifications and removes expired ones (ttl == 0).\n"
).
-spec tick(notification_queue()) -> notification_queue().
tick(Q) ->
    Items = begin
        _pipe = erlang:element(2, Q),
        _pipe@1 = gleam@list:map(_pipe, fun(N) -> case erlang:element(4, N) of
                    -1 ->
                        N;

                    T ->
                        {notification,
                            erlang:element(2, N),
                            erlang:element(3, N),
                            T - 1}
                end end),
        gleam@list:filter(_pipe@1, fun(N@1) -> erlang:element(4, N@1) /= 0 end)
    end,
    {notification_queue, Items, erlang:element(3, Q), erlang:element(4, Q)}.

-file("src/etui/widgets/notification.gleam", 135).
?DOC(" Dismiss all notifications matching `level`.\n").
-spec dismiss_level(notification_queue(), level()) -> notification_queue().
dismiss_level(Q, Level) ->
    {notification_queue,
        gleam@list:filter(
            erlang:element(2, Q),
            fun(N) -> erlang:element(3, N) /= Level end
        ),
        erlang:element(3, Q),
        erlang:element(4, Q)}.

-file("src/etui/widgets/notification.gleam", 143).
?DOC(" Dismiss all notifications.\n").
-spec dismiss_all(notification_queue()) -> notification_queue().
dismiss_all(Q) ->
    {notification_queue, [], erlang:element(3, Q), erlang:element(4, Q)}.

-file("src/etui/widgets/notification.gleam", 148).
?DOC(" Dismiss the oldest notification.\n").
-spec dismiss_first(notification_queue()) -> notification_queue().
dismiss_first(Q) ->
    case erlang:element(2, Q) of
        [] ->
            Q;

        [_ | Rest] ->
            {notification_queue,
                Rest,
                erlang:element(3, Q),
                erlang:element(4, Q)}
    end.

-file("src/etui/widgets/notification.gleam", 156).
?DOC(" True if there are active notifications.\n").
-spec has_notifications(notification_queue()) -> boolean().
has_notifications(Q) ->
    not gleam@list:is_empty(erlang:element(2, Q)).

-file("src/etui/widgets/notification.gleam", 161).
?DOC(" Count of active notifications.\n").
-spec count(notification_queue()) -> integer().
count(Q) ->
    erlang:length(erlang:element(2, Q)).

-file("src/etui/widgets/notification.gleam", 264).
-spec level_colors(level()) -> {etui@style:color(), etui@style:color()}.
level_colors(Level) ->
    case Level of
        info ->
            {{indexed, 15}, {indexed, 4}};

        success ->
            {{indexed, 15}, {indexed, 2}};

        warning ->
            {{indexed, 0}, {indexed, 3}};

        error ->
            {{indexed, 15}, {indexed, 1}}
    end.

-file("src/etui/widgets/notification.gleam", 181).
-spec render_items(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    list(notification()),
    corner(),
    integer()
) -> etui@buffer:buffer().
render_items(Buf, Area, Items, Corner, Index) ->
    case Items of
        [] ->
            Buf;

        [N | Rest] ->
            Box_h = 3,
            Msg_w = etui@text:cell_width(erlang:element(2, N)),
            Box_w = case (Msg_w + 4) > 30 of
                true ->
                    case (Msg_w + 4) > erlang:element(
                        2,
                        erlang:element(3, Area)
                    ) of
                        true ->
                            erlang:element(2, erlang:element(3, Area));

                        false ->
                            Msg_w + 4
                    end;

                false ->
                    30
            end,
            {X, Y} = case Corner of
                top_right ->
                    {(erlang:element(2, erlang:element(2, Area)) + erlang:element(
                            2,
                            erlang:element(3, Area)
                        ))
                        - Box_w,
                        erlang:element(3, erlang:element(2, Area)) + (Index * Box_h)};

                top_left ->
                    {erlang:element(2, erlang:element(2, Area)),
                        erlang:element(3, erlang:element(2, Area)) + (Index * Box_h)};

                bottom_right ->
                    {(erlang:element(2, erlang:element(2, Area)) + erlang:element(
                            2,
                            erlang:element(3, Area)
                        ))
                        - Box_w,
                        ((erlang:element(3, erlang:element(2, Area)) + erlang:element(
                            3,
                            erlang:element(3, Area)
                        ))
                        - Box_h)
                        - (Index * Box_h)};

                bottom_left ->
                    {erlang:element(2, erlang:element(2, Area)),
                        ((erlang:element(3, erlang:element(2, Area)) + erlang:element(
                            3,
                            erlang:element(3, Area)
                        ))
                        - Box_h)
                        - (Index * Box_h)}
            end,
            Fits = (((X >= erlang:element(2, erlang:element(2, Area))) andalso (Y
            >= erlang:element(3, erlang:element(2, Area))))
            andalso ((X + Box_w) =< (erlang:element(2, erlang:element(2, Area))
            + erlang:element(2, erlang:element(3, Area)))))
            andalso ((Y + Box_h) =< (erlang:element(3, erlang:element(2, Area))
            + erlang:element(3, erlang:element(3, Area)))),
            Buf2 = case Fits of
                false ->
                    Buf;

                true ->
                    Box_area = {rect, {position, X, Y}, {size, Box_w, Box_h}},
                    {Fg, Bg} = level_colors(erlang:element(3, N)),
                    Blk = begin
                        _pipe = etui@widgets@block:block_new(),
                        _pipe@1 = etui@widgets@block:with_border(_pipe, rounded),
                        _pipe@2 = etui@widgets@block:with_style(_pipe@1, Fg, Bg),
                        etui@widgets@block:with_bg_fill(_pipe@2)
                    end,
                    Buf_b = etui@widgets@block:render(Buf, Box_area, Blk),
                    Inner = etui@widgets@block:inner(Box_area, Blk),
                    Msg_x = erlang:element(2, erlang:element(2, Inner)) + ((erlang:element(
                        2,
                        erlang:element(3, Inner)
                    )
                    - etui@text:cell_width(erlang:element(2, N)))
                    div 2),
                    case (erlang:element(2, erlang:element(3, Inner)) > 0)
                    andalso (erlang:element(3, erlang:element(3, Inner)) > 0) of
                        false ->
                            Buf_b;

                        true ->
                            etui@buffer:set_string(
                                Buf_b,
                                {position,
                                    Msg_x,
                                    erlang:element(3, erlang:element(2, Inner))},
                                etui@text:truncate(
                                    erlang:element(2, N),
                                    erlang:element(2, erlang:element(3, Inner)),
                                    <<"…"/utf8>>
                                ),
                                Fg,
                                Bg,
                                etui@style:none()
                            )
                    end
            end,
            render_items(Buf2, Area, Rest, Corner, Index + 1)
    end.

-file("src/etui/widgets/notification.gleam", 170).
?DOC(
    " Render all active notifications stacked in the configured corner.\n"
    " Each notification is a single-row bordered box; they stack inward.\n"
).
-spec render(etui@buffer:buffer(), etui@geometry:rect(), notification_queue()) -> etui@buffer:buffer().
render(Buf, Area, Q) ->
    case (gleam@list:is_empty(erlang:element(2, Q)) orelse (erlang:element(
        2,
        erlang:element(3, Area)
    )
    =< 0))
    orelse (erlang:element(3, erlang:element(3, Area)) =< 0) of
        true ->
            Buf;

        false ->
            render_items(
                Buf,
                Area,
                erlang:element(2, Q),
                erlang:element(4, Q),
                0
            )
    end.