Module Notify EnableExplicit ;- Toast constants #Toast_W = 300 #Toast_H = 60 #Toast_Margin = 10 #Accent_W = 4 #Toast_Timer = 0 #MaxToasts = 5 ;- Globals Global _Font = LoadFont(#PB_Any, "sans-serif", 12) ; Toast state Global Dim _Wins.i(#MaxToasts) Global Dim _Canvases.i(#MaxToasts) Global Dim _Types.i(#MaxToasts) Global Dim _Messages.s(#MaxToasts) Global _Count ; Confirm state Global _ConfirmWin Global _ConfirmOkBtn Global _ConfirmCancelBtn Global *_ConfirmCb ;- Private declarations Declare _TypeColor(Type) Declare _DrawToast(Slot) Declare _Dismiss(Slot) Declare _Reposition() Declare _FindByWindow(Win) Declare _FindByCanvas(Canvas) Declare _CloseConfirm() Declare _FireConfirm(Result) Declare Handler_ToastTimer() Declare Handler_ToastClick() Declare Handler_ConfirmOK() Declare Handler_ConfirmCancel() Declare Handler_ConfirmClose() ;- Public procedures ; Show a toast in the bottom-right corner. Dismisses after Duration ms or on click. Procedure Toast(Message.s, Type = #Info, Duration = 4000) Protected Slot, DW, DH, X, Y, Win, Canvas If _Count >= #MaxToasts : ProcedureReturn : EndIf Slot = _Count DW = DesktopWidth(0) DH = DesktopHeight(0) X = DW - #Toast_W - #Toast_Margin Y = DH - (Slot + 1) * (#Toast_H + #Toast_Margin) Win = OpenWindow(#PB_Any, X, Y, #Toast_W, #Toast_H, "", #PB_Window_BorderLess) StickyWindow(Win, #True) Canvas = CanvasGadget(#PB_Any, 0, 0, #Toast_W, #Toast_H) _Wins(Slot) = Win _Canvases(Slot) = Canvas _Types(Slot) = Type _Messages(Slot) = Message _Count + 1 _DrawToast(Slot) BindGadgetEvent(Canvas, @Handler_ToastClick()) AddWindowTimer(Win, #Toast_Timer, Duration) BindEvent(#PB_Event_Timer, @Handler_ToastTimer(), Win) EndProcedure ; Show a modal confirm dialog. *Callback(Result) is called with 1 for OK, 0 for Cancel. ; Only one confirm can be open at a time — subsequent calls are ignored. Procedure Confirm(Title.s, Message.s, *Callback) Protected W, H, DW, DH If IsWindow(_ConfirmWin) : ProcedureReturn : EndIf W = 340 H = 130 DW = DesktopWidth(0) DH = DesktopHeight(0) _ConfirmWin = OpenWindow(#PB_Any, (DW - W) / 2, (DH - H) / 2, W, H, Title, #PB_Window_TitleBar | #PB_Window_SystemMenu) StickyWindow(_ConfirmWin, #True) *_ConfirmCb = *Callback TextGadget(#PB_Any, 20, 20, W - 40, 50, Message) _ConfirmOkBtn = ButtonGadget(#PB_Any, W - 190, H - 45, 80, 30, "OK") _ConfirmCancelBtn = ButtonGadget(#PB_Any, W - 100, H - 45, 80, 30, "Cancel") BindGadgetEvent(_ConfirmOkBtn, @Handler_ConfirmOK()) BindGadgetEvent(_ConfirmCancelBtn, @Handler_ConfirmCancel()) BindEvent(#PB_Event_CloseWindow, @Handler_ConfirmClose(), _ConfirmWin) EndProcedure ;- Private procedures Procedure _TypeColor(Type) Select Type Case #Success : ProcedureReturn RGB( 60, 180, 100) Case #Warning : ProcedureReturn RGB(220, 160, 40) Case #Error : ProcedureReturn RGB(210, 65, 65) Default : ProcedureReturn RGB( 50, 130, 220) ; #Info EndSelect EndProcedure Procedure _DrawToast(Slot) Protected W, H, MaxW Protected Msg.s If Not IsGadget(_Canvases(Slot)) : ProcedureReturn : EndIf If Not StartDrawing(CanvasOutput(_Canvases(Slot))) : ProcedureReturn : EndIf W = OutputWidth() H = OutputHeight() ; Background Box(0, 0, W, H, RGB(38, 38, 54)) ; Accent bar Box(0, 0, #Accent_W, H, _TypeColor(_Types(Slot))) ; Message — truncate with ellipsis if too wide DrawingMode(#PB_2DDrawing_Transparent) If IsFont(_Font) : DrawingFont(FontID(_Font)) : EndIf Msg = _Messages(Slot) MaxW = W - #Accent_W - 24 While Len(Msg) > 3 And TextWidth(Msg) > MaxW Msg = Left(Msg, Len(Msg) - 4) + "..." Wend DrawText(#Accent_W + 14, (H - TextHeight("A")) / 2, Msg, RGB(220, 220, 220)) StopDrawing() EndProcedure Procedure _Dismiss(Slot) Protected i If Not IsWindow(_Wins(Slot)) : ProcedureReturn : EndIf UnbindEvent(#PB_Event_Timer, @Handler_ToastTimer(), _Wins(Slot)) UnbindGadgetEvent(_Canvases(Slot), @Handler_ToastClick()) CloseWindow(_Wins(Slot)) For i = Slot To _Count - 2 _Wins(i) = _Wins(i + 1) _Canvases(i) = _Canvases(i + 1) _Types(i) = _Types(i + 1) _Messages(i) = _Messages(i + 1) Next _Wins(_Count - 1) = 0 _Canvases(_Count - 1) = 0 _Types(_Count - 1) = 0 _Messages(_Count - 1) = "" _Count - 1 _Reposition() EndProcedure Procedure _Reposition() Protected DW, DH, i DW = DesktopWidth(0) DH = DesktopHeight(0) For i = 0 To _Count - 1 ResizeWindow(_Wins(i), DW - #Toast_W - #Toast_Margin, DH - (i + 1) * (#Toast_H + #Toast_Margin), #PB_Ignore, #PB_Ignore) Next EndProcedure Procedure _FindByWindow(Win) Protected i For i = 0 To _Count - 1 If _Wins(i) = Win : ProcedureReturn i : EndIf Next ProcedureReturn -1 EndProcedure Procedure _FindByCanvas(Canvas) Protected i For i = 0 To _Count - 1 If _Canvases(i) = Canvas : ProcedureReturn i : EndIf Next ProcedureReturn -1 EndProcedure Procedure _CloseConfirm() UnbindGadgetEvent(_ConfirmOkBtn, @Handler_ConfirmOK()) UnbindGadgetEvent(_ConfirmCancelBtn, @Handler_ConfirmCancel()) UnbindEvent(#PB_Event_CloseWindow, @Handler_ConfirmClose(), _ConfirmWin) CloseWindow(_ConfirmWin) _ConfirmWin = 0 EndProcedure Procedure _FireConfirm(Result) _CloseConfirm() If *_ConfirmCb !notify$g__confirmcb(v_result); *_ConfirmCb = 0 EndIf EndProcedure ;- Event handlers Procedure Handler_ToastTimer() Protected Slot = _FindByWindow(EventWindow()) If Slot >= 0 : _Dismiss(Slot) : EndIf EndProcedure Procedure Handler_ToastClick() Protected Slot If EventType() <> #PB_EventType_LeftButtonUp : ProcedureReturn : EndIf Slot = _FindByCanvas(EventGadget()) If Slot >= 0 : _Dismiss(Slot) : EndIf EndProcedure Procedure Handler_ConfirmOK() _FireConfirm(1) EndProcedure Procedure Handler_ConfirmCancel() _FireConfirm(0) EndProcedure Procedure Handler_ConfirmClose() _FireConfirm(0) EndProcedure EndModule ; IDE Options = SpiderBasic 3.20 (Windows - x86) ; CursorPosition = 13 ; Folding = BAw ; iOSAppOrientation = 0 ; AndroidAppCode = 0 ; AndroidAppOrientation = 0 ; EnableXP ; DPIAware ; CompileSourceDirectory