-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
GUI: Don't redraw whole dialog in Widget::setEnabled() #4962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part of the codebase is really complex and needs a big effort to deep dive into.
I think there are interesting ideas here but some changes seem incomplete and don't cover all the cases.
Currently, I suspect merging this would lead to difficult to diagnose bugs.
gui/gui-manager.cpp
Outdated
| // performance hog. | ||
| if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 2) | ||
| // Also, if redrawing only the top dialog, don't apply shading at all. | ||
| if ((_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 2) || _redrawStatus == kRedrawTopDialog) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't find this move more readable: we loose track about when to not shade when reading afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main motivation behind this and "default buffer" and clearAll changes is that I was losing track what is set where and why.
So for instance we have a None set for open dialog and stack size > 2 but also somewhere deep in the switch/case we skip shading completely. Then "None" is also a valid value you can get from the ".Shading" XML node, i.e. you may be skipping even the "None" case there. So I wanted to have all None sources at one place otherwise it made exactly the opposite impression that if if (_redrawStatus != kRedrawTopDialog) is true, we always apply some shading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my proposal in lephilousophe@rework-redraw
I moved all the conditions when applying shading instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I am concerned - 👍 , it's really a nice refactoring. Just one note: shading = (ThemeEngine::ShadingStyle)xmlEval()->getVar("Dialog." + _dialogStack.top()->_name + ".Shading", 0); can also lead to kShadingNone (typically - tooltips) so you can still get your complex condition being true and yet apply only an empty shading.
For that reason I'm still inclined to go with the "set in the beginning and always apply" path but that's just a purist's argument, readability is good in both cases.
d1ebc2c to
bda5eda
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lephilousophe rebased on master and applied your suggestion to make the disabled stated a child of idle.
Also added an explanation for the removal of markAsDirty calls.
There's a couple of threads to discuss left, so I'll wait for your feedback there.
gui/gui-manager.cpp
Outdated
| // performance hog. | ||
| if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 2) | ||
| // Also, if redrawing only the top dialog, don't apply shading at all. | ||
| if ((_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 2) || _redrawStatus == kRedrawTopDialog) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main motivation behind this and "default buffer" and clearAll changes is that I was losing track what is set where and why.
So for instance we have a None set for open dialog and stack size > 2 but also somewhere deep in the switch/case we skip shading completely. Then "None" is also a valid value you can get from the ".Shading" XML node, i.e. you may be skipping even the "None" case there. So I wanted to have all None sources at one place otherwise it made exactly the opposite impression that if if (_redrawStatus != kRedrawTopDialog) is true, we always apply some shading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lephilousophe See my comment about kShadingNone. Apart from that, I'm all in favour to merge the refactored redraw and rebase my PR on top of it.
|
I just rebased your PR on my commit all of it inside your PR. :) |
2a466bc to
6fff169
Compare
When calling any of: - Widget::setEnabled - EditTextWidget::setEditString - EditableWidget::setEditString - StaticTextWidget::setLabel there is no need to call neither GuiManager::scheduleTopDialogRedraw nor Widget::markAsDirty afterwards -- they set up the call by themselves. Also, refactor a couple of code blocks into calling just GuiManager::redrawFull as it does the same thing.
Until now, every (even the tiniest) widget's change of the enabled state resulted in a redraw of a complete *dialog*. Why this was needed and how it has been fixed requires a bit of explanation. There are two buffers to render the widgets in: Backbuffer and Screen (selected by ThemeEngine::drawToBackbuffer() and ThemeEngine::drawToScreen() respectively, setting VectorRenderer::_activeSurface). Then there are two layers/flags: kDrawLayerBackground and kDrawLayerForeground (selected in Dialog::drawDialog(), setting ThemeEngine::_layerToDraw). When asked for a complete dialog rebuild in GuiManager::redraw() (kRedrawCloseDialog, kRedrawFull) the widgets of every dialog, regardless of their layer, are drawn into Backbuffer and then copied into Screen. When asked for a partial dialog rebuild (kRedrawOpenDialog, kRedrawTopDialog) the widgets of the topmost dialog marked with kDrawLayerBackground are drawn into Backbuffer, then copied into Screen and finally the widgets marked with kDrawLayerForeground are drawn into Screen *only*. When redraw() is called just within the GuiManager's event loop the widgets of the topmost dialog are drawn into Screen *only*. And this is where the layers become important. When rebuilding the dialog, it doesn't really matter which layer has been defined for a widget: Backbuffer contains the ones with kDrawLayerBackground and Screen will supply the rest with kDrawLayerForeground, if needed. But which layer is taken into account when calling Dialog::drawWidgets() ? It is important to realize that the content of Backbuffer is defined by the widget's initial state (idle or disabled): so Backbuffer will contain either "idle color" or "disabled color" after dialog's creation. ThemeEngine::drawDD() does two checks: 1. if widget has kDrawLayerBackground set *and* _activeSurface is Screen, copy the widget from Backbuffer to Screen 2. if widget's layer is the same as _layerToDraw, draw the widget into _activeSurface This is what happens in redraw(kRedrawDisabled) for kDrawLayerBackground widgets: - Backbuffer contains an idle/disabled (depending on its initial state) rendition of the widget - widget is copied from Backbuffer to Screen (1st check in drawDD()) - widget is not drawn into Screen as _layerToDraw is kDrawLayerForeground (2nd check in drawDD()) Summary: when switching between idle/disabled state, widget's color is not updated. This is what happens in redraw(kRedrawDisabled) for kDrawLayerForeground widgets: - Backbuffer contains an idle/disabled (depending on its initial state) rendition of the widget - widget is not copied from Backbuffer to Screen as widget has kDrawLayerForeground set (1st check in drawDD()) - widget is drawn into Screen as _layerToDraw is still kDrawLayerForeground from the last redraw() (2nd check in drawDD()) Summary: when switching between idle/disabled state, widget's color is correctly updated and not restored from Backbuffer. Initially, I set up "button idle" to be rendered in the foreground layer, same as "button disabled". However @lephilousophe suggested a great improvement: render "button idle" still in the background but make "button disabled" its child (in the foreground). Worked like a charm as it just mimics the hovering behaviour. And this is why hovering doesn't require scheduleTopDialogRedraw(): - Backbuffer contains an idle [kDrawLayerBackground] rendition of the widget - when highlighted [kDrawLayerForeground], widget is not copied from Backbuffer to Screen - widget is drawn into Screen Unhovering: - Backbuffer contains an idle [kDrawLayerBackground] rendition of the widget - when idle [kDrawLayerBackground], widget is copied from Backbuffer to Screen - widget is not drawn into Screen
|
@lephilousophe I have no problem with that, so rebased on master & pushed. Once again thanks for your thorough review. |
There are three commits presented but the second one, despite its smallness, is the most interesting one. I spent several hours on this and even now I'm not 100% sure whether I got the theme code right.
You might ask why I didn't change also kDDCHeckbox*, kDDRadiobutton* and kDDPopUp* as those also contain the idle/disabled pairs. The embarrassing answer is that a) it doesn't work (sudden flashing, missing parts of the widgets etc) and b) I didn't understand its code. So either:
So I left those as they were and intensively tested the button and dropdown button, as those were the problematic ones in the launcher. If it ain't broke, don't fix it, right? Of course, I also played with all the dialogs so see any regressions. If you can think of a problematic scenario (involving having a button / widget which can get enabled/disabled in real time without Apply / opening a new dialog etc = without forcing a full redraw), please do let me know.
Content of the commit message for better visibility:
Until now, every (even the tiniest) widget's change of the enabled state
resulted in a redraw of a complete dialog. Why this was needed and how
it has been fixed requires a bit of explanation.
There are two buffers to render the widgets in:
BackbufferandScreen(selected by
ThemeEngine::drawToBackbuffer()andThemeEngine::drawToScreen()respectively, settingVectorRenderer::_activeSurface). Then there are two layers/flags:kDrawLayerBackgroundandkDrawLayerForeground(selected in
Dialog::drawDialog(), settingThemeEngine::_layerToDraw).When asked for a complete dialog rebuild in
GuiManager::redraw()(
kRedrawCloseDialog,kRedrawFull) the widgets of every dialog,regardless of their layer, are drawn into
Backbufferand then copiedinto
Screen.When asked for a partial dialog rebuild (
kRedrawOpenDialog,kRedrawTopDialog) the widgets of the topmost dialog marked withkDrawLayerBackgroundare drawn intoBackbuffer, then copied intoScreenand finally the widgets marked with
kDrawLayerForegroundare drawn intoScreenonly.When
redraw()is called just within theGuiManager's event loop thewidgets of the topmost dialog are drawn into
Screenonly. And this iswhere the layers become important.
When rebuilding the dialog, it doesn't really matter which layer has
been defined for a widget:
Backbuffercontains the ones withkDrawLayerBackgroundandScreenwill supply the rest withkDrawLayerForeground, if needed. But which layer is taken into accountwhen calling
Dialog::drawWidgets()?It is important to realize that the content of
Backbufferisdefined by the widget's initial state (idle or disabled): so
Backbufferwill contain either "idle color" or "disabled color" after dialog's
creation.
ThemeEngine::drawDD()does two checks:if widget has
kDrawLayerBackgroundset and_activeSurfaceisScreen, copy the widget fromBackbuffertoScreenif widget's layer is the same as
_layerToDraw, draw the widget into_activeSurfaceThis is what happens in
redraw(kRedrawDisabled)forkDrawLayerBackgroundwidgets:Backbuffercontains an idle/disabled (depending on its initial state) rendition of the widgetBackbuffertoScreen(1st check indrawDD())Screenas_layerToDrawiskDrawLayerForegroundfrom the lastredraw()(2nd check indrawDD())Summary: when switching between idle/disabled state, widget's color is not updated.
This is what happens in
redraw(kRedrawDisabled)forkDrawLayerForegroundwidgets:Backbuffercontains an idle/disabled (depending on its initial state) rendition of the widgetBackbuffertoScreenas widget haskDrawLayerForegroundset (1st check indrawDD())Screenas_layerToDrawiskDrawLayerForegroundfrom the lastredraw()(2nd check indrawDD())Summary: when switching between idle/disabled state, widget's color is correctly updated and not restored from
Backbuffer.This is why hovering didn't require
scheduleTopDialogRedraw():Backbuffercontains an idle [kDrawLayerBackground] rendition of the widgetkDrawLayerForeground], widget is not copied fromBackbuffertoScreenScreenUnhovering:
Backbuffercontains an idle [kDrawLayerBackground] rendition of the widgetkDrawLayerBackground], widget is copied fromBackbuffertoScreenScreenThe key difference here is that highlighting can't be stored as initial
state. If every widget had to be enabled in dialog redraw, only the
"disabled" widgets would had to be set to
kDrawLayerForeground.