From 501d6282f793e4461f2292e41661155fff988ebd Mon Sep 17 00:00:00 2001 From: Dimitar Tsenev Date: Wed, 19 Aug 2026 17:42:49 +0300 Subject: [PATCH 1/2] [GUI] Fix immediate mode transparency and compositing order This commit addresses issues with how transparent, alpha-blended, gradient-blended, and rounded-corner widgets (especially root widgets) are composited in immediate rendering mode. The background and any underlying widgets must be painted *before* overlapping transparent elements to ensure correct blending. Changes include: - Modifying `calcWidgetClipRegion` to ensure areas under transparent widgets remain visible for underlying layers. - Refactoring `paint()` to ensure the desktop background is painted first in immediate mode, and root widgets are iterated back-to-front for painting. - Removing pre-clearing in `paintLayer` to allow blending against existing content. - Adjusting `addRootWidget` to ensure widgets with identical z-positions are consistently stacked with the last-added on top. - Ensuring `eWindow` also sets alpha blend on its child widget for visibility. --- lib/gui/ewidgetdesktop.cpp | 84 ++++++++++++++++++++++++++------------ lib/gui/ewidgetdesktop.h | 4 +- lib/gui/ewindow.cpp | 10 +++++ lib/gui/ewindow.h | 1 + 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/lib/gui/ewidgetdesktop.cpp b/lib/gui/ewidgetdesktop.cpp index 4d37530f09e..b3aef05f330 100644 --- a/lib/gui/ewidgetdesktop.cpp +++ b/lib/gui/ewidgetdesktop.cpp @@ -10,15 +10,22 @@ void eWidgetDesktop::addRootWidget(eWidget *root) ASSERT(!root->m_desktop); int invert_sense = 0; - /* buffered mode paints back-to-front, while immediate mode is front-to-back. */ + /* m_root is always kept sorted front-to-back in immediate mode (used for + clip region calculation, front to back), and back-to-front in buffered + mode (used for composition, back to front). immediate mode painting + itself walks m_root in reverse (back-to-front) -- see paint(). */ if (m_comp_mode == cmBuffered) invert_sense = 1; ePtrList::iterator insert_position = m_root.begin(); + /* <= rather than < : among widgets sharing the same (e.g. default, + unset) zPosition, the one added last ends up on top, consistent + with how child widgets are stacked (see eWidget::insertIntoParent) + and with the painting order below. */ for (;;) { - if ((insert_position == m_root.end()) || (invert_sense ^ (insert_position->m_z_position < root->m_z_position))) + if ((insert_position == m_root.end()) || (invert_sense ^ (insert_position->m_z_position <= root->m_z_position))) { m_root.insert(insert_position, root); break; @@ -59,7 +66,7 @@ int eWidgetDesktop::movedWidget(eWidget *root) return 0; /* native move ok */ } -void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible, bool parent) +void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible) { /* start with our clip region, clipped with the parent's */ if (widget->m_vis & eWidget::wVisShow) @@ -68,8 +75,13 @@ void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visib widget->m_visible_region.moveBy(widget->position()); widget->m_visible_region &= parent_visible; // in parent space! - if (!widget->isTransparent() && (!widget->m_gradient_alphablend || parent) && (widget->m_cornerRadius == 0 || parent) && (!widget->m_alphaBlend || parent)) - /* remove everything this widget will contain from parent's visible list, unless widget is transparent. */ + if (!widget->isTransparent() && !widget->m_gradient_alphablend && widget->m_cornerRadius == 0 && !widget->m_alphaBlend) + /* remove everything this widget will contain from parent's visible list, unless the + widget is transparent, alphablended, gradient-alphablended or rounded -- in that + case whatever is behind it (a parent widget, another root widget, or the desktop + background) must stay available so it keeps being calculated/painted underneath. + this applies the same way at root (screen) level as it does for nested widgets, + so overlapping root windows respect zPosition and composite correctly. */ parent_visible -= widget->m_visible_region; // will remove child regions too! /* now prepare for recursing to childs */ @@ -87,7 +99,7 @@ void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visib if (i != widget->m_childs.end()) { if (i->m_vis & eWidget::wVisShow) - calcWidgetClipRegion(*i, widget->m_visible_region, false); + calcWidgetClipRegion(*i, widget->m_visible_region); else clearVisibility(*i); } @@ -275,20 +287,21 @@ void eWidgetDesktop::setPalette(gPixmap &pm) } } -void eWidgetDesktop::paintBackground(eWidgetDesktopCompBuffer *comp) +void eWidgetDesktop::paintBackground(eWidgetDesktopCompBuffer *comp, bool clearDirty) { if (!comp) return; - comp->m_dirty_region &= comp->m_background_region; + gRegion background_dirty = comp->m_dirty_region & comp->m_background_region; gPainter painter(comp->m_dc); - painter.resetClip(comp->m_dirty_region); + painter.resetClip(background_dirty); painter.setBackgroundColor(comp->m_background_color); painter.clear(); - comp->m_dirty_region = gRegion(); + if (clearDirty) + comp->m_dirty_region = gRegion(); } @@ -301,12 +314,13 @@ void eWidgetDesktop::paintLayer(eWidget *widget, int layer) return; gPainter painter(comp->m_dc); painter.moveOffset(-comp->m_position); - if (widget->m_cornerRadius > 0 || widget->m_gradient_set || widget->m_alphaBlend) - { - painter.resetClip(comp->m_dirty_region); - painter.setBackgroundColor(gRGB(0, 0, 0, 0xFF)); - painter.clear(); - } + /* no pre-clear here: alphablended/gradient/rounded-corner drawing (see + gPixmap::drawRectangle/drawRectangleNew) blends against whatever is + already in the destination, and rounded corners outside the radius + are deliberately left untouched. Both rely on the real content behind + this widget -- the desktop background or another root widget -- having + already been painted there (see paint()); clearing to a fixed color + first would blend/leave that fixed color instead of what's behind. */ widget->doPaint(painter, comp->m_dirty_region, layer); painter.resetOffset(); } @@ -315,26 +329,44 @@ void eWidgetDesktop::paint() { m_require_redraw = 0; - /* walk all root windows. */ - for (ePtrList::iterator i(m_root.begin()); i != m_root.end(); ++i) + if (m_comp_mode == cmImmediate) { + /* paint the true (video) background first: widgets with a transparent, + alphablended, gradient-alphablended or rounded-corner background rely + on whatever is behind them -- the desktop background, or another root + widget -- already being painted, so they can composite on top of it. */ + paintBackground(&m_screen, false); + + /* m_root is sorted front-to-back (see addRootWidget). paint it back-to- + front, so widgets further back are painted first and the ones in front + of them (which may be transparent/blended) composite correctly on top, + respecting zPosition. */ + for (ePtrList::reverse_iterator i(m_root.rbegin()); i != m_root.rend(); ++i) + { + if (!(i->m_vis & eWidget::wVisShow)) + continue; - if (!(i->m_vis & eWidget::wVisShow)) - continue; - - if (m_comp_mode == cmImmediate) paintLayer(i, 0); - else + } + + m_screen.m_dirty_region = gRegion(); + } + else + { + /* walk all root windows. */ + for (ePtrList::iterator i(m_root.begin()); i != m_root.end(); ++i) + { + if (!(i->m_vis & eWidget::wVisShow)) + continue; + for (int l = 0; l < MAX_LAYER; ++l) { paintLayer(i, l); paintBackground(i->m_comp_buffer[l]); } + } } - if (m_comp_mode == cmImmediate) - paintBackground(&m_screen); - if (m_comp_mode == cmBuffered) { // redrawComposition(0); diff --git a/lib/gui/ewidgetdesktop.h b/lib/gui/ewidgetdesktop.h index a4c39e0adfc..c5527e48b35 100644 --- a/lib/gui/ewidgetdesktop.h +++ b/lib/gui/ewidgetdesktop.h @@ -80,8 +80,8 @@ class eWidgetDesktop: public sigc::trackable void setMargins(const eRect& value) { m_margins = value; } private: ePtrList m_root; - void calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible, bool parent = true); - void paintBackground(eWidgetDesktopCompBuffer *comp); + void calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible); + void paintBackground(eWidgetDesktopCompBuffer *comp, bool clearDirty = true); eMainloop *m_mainloop; ePtr m_timer; diff --git a/lib/gui/ewindow.cpp b/lib/gui/ewindow.cpp index f05f1073a58..64d7a43fa3a 100644 --- a/lib/gui/ewindow.cpp +++ b/lib/gui/ewindow.cpp @@ -170,3 +170,13 @@ void eWindow::setCornerRadius(int radius, uint8_t edges) eWidget::setCornerRadius(radius, edges); m_child->setCornerRadius(radius, edges); } + +void eWindow::setWidgetAlphaBlend(bool blend) +{ + /* set alphablend for child, too -- the window itself only ever paints its + border/title decoration (see event(evtPaint)), the actual background is + painted by m_child, so it needs to alphablend as well for a screen-level + alphaBlend skin attribute to have any visible effect. */ + eWidget::setWidgetAlphaBlend(blend); + m_child->setWidgetAlphaBlend(blend); +} diff --git a/lib/gui/ewindow.h b/lib/gui/ewindow.h index 66557a01e4e..a76202758ae 100644 --- a/lib/gui/ewindow.h +++ b/lib/gui/ewindow.h @@ -25,6 +25,7 @@ class eWindow: public eWidget void setBackgroundColor(const gRGB &col) override; void setBackgroundGradient(const gRGB &startcolor, const gRGB &midcolor, const gRGB &endcolor, uint8_t direction, bool alphablend); void setCornerRadius(int radius, uint8_t edges); + void setWidgetAlphaBlend(bool blend) override; void setFlag(int flags); void clearFlag(int flags); From d4b4aa8ac68a6a37d6296ebf9cad3c95b7e0c673 Mon Sep 17 00:00:00 2001 From: Dimitar Tsenev Date: Wed, 19 Aug 2026 17:42:49 +0300 Subject: [PATCH 2/2] [GUI] Fix immediate mode transparency and compositing order This commit addresses issues with how transparent, alpha-blended, gradient-blended, and rounded-corner widgets (especially root widgets) are composited in immediate rendering mode. The background and any underlying widgets must be painted *before* overlapping transparent elements to ensure correct blending. Changes include: - Modifying `calcWidgetClipRegion` to ensure areas under transparent widgets remain visible for underlying layers. - Refactoring `paint()` to ensure the desktop background is painted first in immediate mode, and root widgets are iterated back-to-front for painting. - Removing pre-clearing in `paintLayer` to allow blending against existing content. - Adjusting `addRootWidget` to ensure widgets with identical z-positions are consistently stacked with the last-added on top. - Ensuring `eWindow` also sets alpha blend on its child widget for visibility. --- lib/gui/ewidgetdesktop.cpp | 84 ++++++++++++++++++++++++++------------ lib/gui/ewidgetdesktop.h | 4 +- lib/gui/ewindow.cpp | 10 +++++ lib/gui/ewindow.h | 1 + 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/lib/gui/ewidgetdesktop.cpp b/lib/gui/ewidgetdesktop.cpp index 4d37530f09e..b3aef05f330 100644 --- a/lib/gui/ewidgetdesktop.cpp +++ b/lib/gui/ewidgetdesktop.cpp @@ -10,15 +10,22 @@ void eWidgetDesktop::addRootWidget(eWidget *root) ASSERT(!root->m_desktop); int invert_sense = 0; - /* buffered mode paints back-to-front, while immediate mode is front-to-back. */ + /* m_root is always kept sorted front-to-back in immediate mode (used for + clip region calculation, front to back), and back-to-front in buffered + mode (used for composition, back to front). immediate mode painting + itself walks m_root in reverse (back-to-front) -- see paint(). */ if (m_comp_mode == cmBuffered) invert_sense = 1; ePtrList::iterator insert_position = m_root.begin(); + /* <= rather than < : among widgets sharing the same (e.g. default, + unset) zPosition, the one added last ends up on top, consistent + with how child widgets are stacked (see eWidget::insertIntoParent) + and with the painting order below. */ for (;;) { - if ((insert_position == m_root.end()) || (invert_sense ^ (insert_position->m_z_position < root->m_z_position))) + if ((insert_position == m_root.end()) || (invert_sense ^ (insert_position->m_z_position <= root->m_z_position))) { m_root.insert(insert_position, root); break; @@ -59,7 +66,7 @@ int eWidgetDesktop::movedWidget(eWidget *root) return 0; /* native move ok */ } -void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible, bool parent) +void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible) { /* start with our clip region, clipped with the parent's */ if (widget->m_vis & eWidget::wVisShow) @@ -68,8 +75,13 @@ void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visib widget->m_visible_region.moveBy(widget->position()); widget->m_visible_region &= parent_visible; // in parent space! - if (!widget->isTransparent() && (!widget->m_gradient_alphablend || parent) && (widget->m_cornerRadius == 0 || parent) && (!widget->m_alphaBlend || parent)) - /* remove everything this widget will contain from parent's visible list, unless widget is transparent. */ + if (!widget->isTransparent() && !widget->m_gradient_alphablend && widget->m_cornerRadius == 0 && !widget->m_alphaBlend) + /* remove everything this widget will contain from parent's visible list, unless the + widget is transparent, alphablended, gradient-alphablended or rounded -- in that + case whatever is behind it (a parent widget, another root widget, or the desktop + background) must stay available so it keeps being calculated/painted underneath. + this applies the same way at root (screen) level as it does for nested widgets, + so overlapping root windows respect zPosition and composite correctly. */ parent_visible -= widget->m_visible_region; // will remove child regions too! /* now prepare for recursing to childs */ @@ -87,7 +99,7 @@ void eWidgetDesktop::calcWidgetClipRegion(eWidget *widget, gRegion &parent_visib if (i != widget->m_childs.end()) { if (i->m_vis & eWidget::wVisShow) - calcWidgetClipRegion(*i, widget->m_visible_region, false); + calcWidgetClipRegion(*i, widget->m_visible_region); else clearVisibility(*i); } @@ -275,20 +287,21 @@ void eWidgetDesktop::setPalette(gPixmap &pm) } } -void eWidgetDesktop::paintBackground(eWidgetDesktopCompBuffer *comp) +void eWidgetDesktop::paintBackground(eWidgetDesktopCompBuffer *comp, bool clearDirty) { if (!comp) return; - comp->m_dirty_region &= comp->m_background_region; + gRegion background_dirty = comp->m_dirty_region & comp->m_background_region; gPainter painter(comp->m_dc); - painter.resetClip(comp->m_dirty_region); + painter.resetClip(background_dirty); painter.setBackgroundColor(comp->m_background_color); painter.clear(); - comp->m_dirty_region = gRegion(); + if (clearDirty) + comp->m_dirty_region = gRegion(); } @@ -301,12 +314,13 @@ void eWidgetDesktop::paintLayer(eWidget *widget, int layer) return; gPainter painter(comp->m_dc); painter.moveOffset(-comp->m_position); - if (widget->m_cornerRadius > 0 || widget->m_gradient_set || widget->m_alphaBlend) - { - painter.resetClip(comp->m_dirty_region); - painter.setBackgroundColor(gRGB(0, 0, 0, 0xFF)); - painter.clear(); - } + /* no pre-clear here: alphablended/gradient/rounded-corner drawing (see + gPixmap::drawRectangle/drawRectangleNew) blends against whatever is + already in the destination, and rounded corners outside the radius + are deliberately left untouched. Both rely on the real content behind + this widget -- the desktop background or another root widget -- having + already been painted there (see paint()); clearing to a fixed color + first would blend/leave that fixed color instead of what's behind. */ widget->doPaint(painter, comp->m_dirty_region, layer); painter.resetOffset(); } @@ -315,26 +329,44 @@ void eWidgetDesktop::paint() { m_require_redraw = 0; - /* walk all root windows. */ - for (ePtrList::iterator i(m_root.begin()); i != m_root.end(); ++i) + if (m_comp_mode == cmImmediate) { + /* paint the true (video) background first: widgets with a transparent, + alphablended, gradient-alphablended or rounded-corner background rely + on whatever is behind them -- the desktop background, or another root + widget -- already being painted, so they can composite on top of it. */ + paintBackground(&m_screen, false); + + /* m_root is sorted front-to-back (see addRootWidget). paint it back-to- + front, so widgets further back are painted first and the ones in front + of them (which may be transparent/blended) composite correctly on top, + respecting zPosition. */ + for (ePtrList::reverse_iterator i(m_root.rbegin()); i != m_root.rend(); ++i) + { + if (!(i->m_vis & eWidget::wVisShow)) + continue; - if (!(i->m_vis & eWidget::wVisShow)) - continue; - - if (m_comp_mode == cmImmediate) paintLayer(i, 0); - else + } + + m_screen.m_dirty_region = gRegion(); + } + else + { + /* walk all root windows. */ + for (ePtrList::iterator i(m_root.begin()); i != m_root.end(); ++i) + { + if (!(i->m_vis & eWidget::wVisShow)) + continue; + for (int l = 0; l < MAX_LAYER; ++l) { paintLayer(i, l); paintBackground(i->m_comp_buffer[l]); } + } } - if (m_comp_mode == cmImmediate) - paintBackground(&m_screen); - if (m_comp_mode == cmBuffered) { // redrawComposition(0); diff --git a/lib/gui/ewidgetdesktop.h b/lib/gui/ewidgetdesktop.h index a4c39e0adfc..c5527e48b35 100644 --- a/lib/gui/ewidgetdesktop.h +++ b/lib/gui/ewidgetdesktop.h @@ -80,8 +80,8 @@ class eWidgetDesktop: public sigc::trackable void setMargins(const eRect& value) { m_margins = value; } private: ePtrList m_root; - void calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible, bool parent = true); - void paintBackground(eWidgetDesktopCompBuffer *comp); + void calcWidgetClipRegion(eWidget *widget, gRegion &parent_visible); + void paintBackground(eWidgetDesktopCompBuffer *comp, bool clearDirty = true); eMainloop *m_mainloop; ePtr m_timer; diff --git a/lib/gui/ewindow.cpp b/lib/gui/ewindow.cpp index f05f1073a58..64d7a43fa3a 100644 --- a/lib/gui/ewindow.cpp +++ b/lib/gui/ewindow.cpp @@ -170,3 +170,13 @@ void eWindow::setCornerRadius(int radius, uint8_t edges) eWidget::setCornerRadius(radius, edges); m_child->setCornerRadius(radius, edges); } + +void eWindow::setWidgetAlphaBlend(bool blend) +{ + /* set alphablend for child, too -- the window itself only ever paints its + border/title decoration (see event(evtPaint)), the actual background is + painted by m_child, so it needs to alphablend as well for a screen-level + alphaBlend skin attribute to have any visible effect. */ + eWidget::setWidgetAlphaBlend(blend); + m_child->setWidgetAlphaBlend(blend); +} diff --git a/lib/gui/ewindow.h b/lib/gui/ewindow.h index 66557a01e4e..a76202758ae 100644 --- a/lib/gui/ewindow.h +++ b/lib/gui/ewindow.h @@ -25,6 +25,7 @@ class eWindow: public eWidget void setBackgroundColor(const gRGB &col) override; void setBackgroundGradient(const gRGB &startcolor, const gRGB &midcolor, const gRGB &endcolor, uint8_t direction, bool alphablend); void setCornerRadius(int radius, uint8_t edges); + void setWidgetAlphaBlend(bool blend) override; void setFlag(int flags); void clearFlag(int flags);