Lagrange [dev]

Fixed memory leak on tab close

=> bb7bc6fac4fec804846d11c7d77e1b553ba2be6a

diff --git a/src/app.c b/src/app.c
index ff5ec9b7..0916919e 100644
--- a/src/app.c
+++ b/src/app.c
@@ -649,12 +649,17 @@ iAny *findWidget_App(const char *id) {
     return findChild_Widget(app_.window->root, id);
 }
 
-void addTicker_App(void (*ticker)(iAny *), iAny *context) {
+void addTicker_App(iTickerFunc ticker, iAny *context) {
     iApp *d = &app_;
     insert_SortedArray(&d->tickers, &(iTicker){ context, ticker });
     postRefresh_App();
 }
 
+void removeTicker_App(iTickerFunc ticker, iAny *context) {
+    iApp *d = &app_;
+    remove_SortedArray(&d->tickers, &(iTicker){ context, ticker });
+}
+
 iGmCerts *certs_App(void) {
     return app_.certs;
 }
@@ -737,7 +742,8 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe
         doc = new_DocumentWidget();
     }
     setId_Widget(as_Widget(doc), format_CStr("document%03d", ++d->tabEnum));
-    appendTabPage_Widget(tabs, iClob(doc), "", 0, 0);
+    appendTabPage_Widget(tabs, as_Widget(doc), "", 0, 0);
+    iRelease(doc); /* now owned by the tabs */
     addChild_Widget(findChild_Widget(tabs, "tabs.buttons"), iClob(newTabButton));
     if (switchToNew) {
         postCommandf_App("tabs.switch page:%p", doc);
diff --git a/src/app.h b/src/app.h
index db22230e..bf310d98 100644
--- a/src/app.h
+++ b/src/app.h
@@ -73,8 +73,11 @@ iObjectList *       listDocuments_App   (void);
 iDocumentWidget *   document_Command    (const char *cmd);
 iDocumentWidget *   newTab_App          (const iDocumentWidget *duplicateOf, iBool switchToNew);
 
+typedef void (*iTickerFunc)(iAny *);
+
 iAny *      findWidget_App      (const char *id);
-void        addTicker_App       (void (*ticker)(iAny *), iAny *context);
+void        addTicker_App       (iTickerFunc ticker, iAny *context);
+void        removeTicker_App    (iTickerFunc ticker, iAny *context);
 void        postRefresh_App     (void);
 void        postCommand_App     (const char *command);
 void        postCommandf_App    (const char *command, ...);
diff --git a/src/audio/player.c b/src/audio/player.c
index 5b9d0103..07f41f01 100644
--- a/src/audio/player.c
+++ b/src/audio/player.c
@@ -563,3 +563,9 @@ float streamProgress_Player(const iPlayer *d) {
     }
     return 0;
 }
+
+iString *metadataLabel_Player(const iPlayer *d) {
+    return newFormat_String("%d-bit %s %d Hz", SDL_AUDIO_BITSIZE(d->decoder->inputFormat),
+                            SDL_AUDIO_ISFLOAT(d->decoder->inputFormat) ? "float" : "integer",
+                            d->spec.freq);
+}
diff --git a/src/audio/player.h b/src/audio/player.h
index c3552640..fe6717b0 100644
--- a/src/audio/player.h
+++ b/src/audio/player.h
@@ -45,3 +45,5 @@ iBool   isPaused_Player         (const iPlayer *);
 float   time_Player             (const iPlayer *);
 float   duration_Player         (const iPlayer *);
 float   streamProgress_Player   (const iPlayer *); /* normalized 0...1 */
+
+iString *   metadataLabel_Player    (const iPlayer *);
diff --git a/src/media.c b/src/media.c
index 253893fc..c447704b 100644
--- a/src/media.c
+++ b/src/media.c
@@ -75,8 +75,8 @@ void deinit_GmImage(iGmImage *d) {
 }
 
 void makeTexture_GmImage(iGmImage *d) {
-    iBlock *data = &d->partialData;
-    d->numBytes = size_Block(data);
+    iBlock *data     = &d->partialData;
+    d->numBytes      = size_Block(data);
     uint8_t *imgData = stbi_load_from_memory(
         constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4);
     if (!imgData) {
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 9d94f200..bbe5ccba 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -191,6 +191,7 @@ struct Impl_DocumentWidget {
     iAnim          outlineOpacity;
     iArray         outline;
     iWidget *      menu;
+    iWidget *      playerMenu;
     iVisBuf *      visBuf;
     iPtrSet *      invalidRuns;
 };
@@ -241,6 +242,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
     init_Click(&d->click, d, SDL_BUTTON_LEFT);
     addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
     d->menu = NULL; /* created when clicking */
+    d->playerMenu = NULL;
 #if !defined (iPlatformApple) /* in system menu */
     addAction_Widget(w, reload_KeyShortcut, "navigate.reload");
     addAction_Widget(w, SDLK_w, KMOD_PRIMARY, "tabs.close");
@@ -249,7 +251,10 @@ void init_DocumentWidget(iDocumentWidget *d) {
     addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward");
 }
 
+static void animatePlayingAudio_DocumentWidget_(void *);
+
 void deinit_DocumentWidget(iDocumentWidget *d) {
+    removeTicker_App(animatePlayingAudio_DocumentWidget_, d);
     delete_VisBuf(d->visBuf);
     delete_PtrSet(d->invalidRuns);
     deinit_Array(&d->outline);
@@ -1626,7 +1631,7 @@ static void drawPlayerButton_(iPaint *p, iRect rect, const char *label) {
             adjusted_Rect(shrunk_Rect(frameRect, divi_I2(gap2_UI, 2)), zero_I2(), one_I2()),
             frame);
     }
-    const int fg = isPressed ? uiBackground_ColorId : frame;
+    const int fg = isPressed ? (permanent_ColorId | uiBackground_ColorId) : uiHeading_ColorId;
     drawCentered_Text(uiContent_FontId, frameRect, iTrue, fg, "%s", label);
 }
 
@@ -1704,6 +1709,11 @@ static iBool processAudioPlayerEvents_DocumentWidget_(iDocumentWidget *d, const
         ev->type != SDL_MOUSEMOTION) {
         return iFalse;
     }
+    if (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) {
+        if (ev->button.button != SDL_BUTTON_LEFT) {
+            return iFalse;
+        }
+    }
     const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
     iConstForEach(PtrArray, i, &d->visiblePlayers) {
         const iGmRun *run = i.ptr;
@@ -1722,12 +1732,34 @@ static iBool processAudioPlayerEvents_DocumentWidget_(iDocumentWidget *d, const
                 return iTrue;
             }
             else if (contains_Rect(ui.rewindRect, mouse)) {
-                stop_Player(plr);
-                start_Player(plr);
-                setPaused_Player(plr, iTrue);
+                if (isStarted_Player(plr) && time_Player(plr) > 0.5f) {
+                    stop_Player(plr);
+                    start_Player(plr);
+                    setPaused_Player(plr, iTrue);
+                }
                 refresh_Widget(d);
                 return iTrue;
             }
+            else if (contains_Rect(ui.menuRect, mouse)) {
+                /* TODO: Add menu items for:
+                   - output device
+                   - Save to Downloads
+                */
+                if (d->playerMenu) {
+                    destroy_Widget(d->playerMenu);
+                    d->playerMenu = NULL;
+                    return iTrue;
+                }
+                d->playerMenu = makeMenu_Widget(
+                    as_Widget(d),
+                    (iMenuItem[]){
+                        { cstrCollect_String(metadataLabel_Player(plr)), 0, 0, NULL },
+                    },
+                    1);
+                openMenu_Widget(d->playerMenu,
+                                localCoord_Widget(constAs_Widget(d), bottomLeft_Rect(ui.menuRect)));
+                return iTrue;
+            }
         }
     }
     return iFalse;
diff --git a/src/ui/util.c b/src/ui/util.c
index 13a7a7a2..89f71da2 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -498,9 +498,8 @@ iWidget *removeTabPage_Widget(iWidget *tabs, size_t index) {
     iWidget *button  = removeChild_Widget(buttons, child_Widget(buttons, index));
     iRelease(button);
     iWidget *page = child_Widget(pages, index);
-    ref_Object(page);
     setFlags_Widget(page, hidden_WidgetFlag | disabled_WidgetFlag, iFalse);
-    removeChild_Widget(pages, page);
+    removeChild_Widget(pages, page); /* `page` is now ours */
     if (tabCount_Widget(tabs) <= 1 && flags_Widget(buttons) & collapse_WidgetFlag) {
         setFlags_Widget(buttons, hidden_WidgetFlag, iTrue);
     }
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 459c2ae1..ea2e3fe2 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -55,7 +55,6 @@ iPtrArray *onTop_RootData_(void) {
 void destroyPending_Widget(void) {
     iForEach(PtrSet, i, rootData_.pendingDestruction) {
         iWidget *widget = *i.value;
-        removeOne_PtrArray(onTop_RootData_(), widget);
         if (widget->parent) {
             iRelease(removeChild_Widget(widget->parent, widget));
         }
@@ -90,6 +89,9 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) {
         setFocus_Widget(NULL);
         return;
     }
+    if (flags_Widget(d) & keepOnTop_WidgetFlag) {
+        removeOne_PtrArray(onTop_RootData_(), d);
+    }
     if (isHover_Widget(d)) {
         rootData_.hover = NULL;
     }
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/dev/cdiff/bb7bc6fac4fec804846d11c7d77e1b553ba2be6a
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
199.599313 milliseconds
Gemini-to-HTML Time
0.505252 milliseconds

This content has been proxied by September (ba2dc).