Skip to content
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

Discussion: Themes vs Styles vs LookAndFeel #77

Open
luchr opened this issue Dec 11, 2022 · 13 comments
Open

Discussion: Themes vs Styles vs LookAndFeel #77

luchr opened this issue Dec 11, 2022 · 13 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@luchr
Copy link
Contributor

luchr commented Dec 11, 2022

This is not a bug-report or a report about a missing feature, but a "question/discussion" issue.

This question came up here

about the look and feel, I always wondered the possibility to allow some widgets to have a "style" attribute but I never formalised it.

Current state

I try to summarize the current state (please correct me, if/when my summary has errors):

  1. In TTkCfg there is an attribute theme which points to the "current" theme
  2. Widgets (e.g. a TTkFrame) check some theme-attributes at construction time (e.g. frameBorderColor, frameTitleColor)
  3. Widgets (e.g. a TTkFrame) check some theme-attributes at every call to paintEvent (e.g. TTkCfg.theme.grid)
  4. Widgets (e.g. a TTkFrame) support by keyword arguments to __init__ to not ask TTkCfg.theme for a color but to use the one explicitly given at construction time with an explicit keyword argument (e.g. titleColor=...)

Because of 3. it is possible to switch/change the grids, button-boxes, scrollbars, menubars, etc. dynamically (on the fly); which can be seen in the demo where one can switch from UTF-8 to ASCII theme.

Because of 2. a change in attributes like frameTitleColor has no effect on the widgets that are already constructed, but has an effect on every widget that is created after the theme-attribute(s) changed.

Because of 4. "many" slots are used (per widget) to save "their" colors (either the explicitly given ones from __init__ or the ones that were found in TTkCfg.theme by __init__).

[Some personal note: The asymmetry of 2. and 3. (getting some theme attributes at construction time and some at every paintEvent) is a little bit ... disturbing ... for me.]

Proposition/Suggestion

Here is an alternative approach which (in my opinion) simplifies things a bit:

Instead of the many color-slots (per widget) every widget hat only one look_and_feel (laf) slot. There a (reference to an) object is saved that is used at every paintEvent to ask the look_and_fool object for colors, grid-chars, borders-chars, etc. The default look_and_feel object is a "proxy" that "redirects" all the attribute-getters to the current TTkCfg.theme. All the widgets that use the default look_and_feel object see every change in TTkCfg.theme immediately (at the next repaint/update). But this scheme allows for other laf-objects: e.g. a look_and_feel-object that gives for some attributes (e.g. frameBorderColor ) some fixed colors that were chosen at the construction time of the laf-object and all the other attributes are proxied to TTkCfg.theme.

  1. (laf-resolution at paintEvent) So one can have laf-objects that (per default) proxy their attribute requests to TTkCfg.theme but have the ability to prescribe some attributes directly without asking TTkCfg.theme. So it's possible to use my_frame_laf = LookAndFeelProxy(); my_frame_laf.frameBorderColor = TTkColor.fg("#ff0000#"); as an laf-object for a frame that has (independent of the current theme) a red border, but all the other things (like the chars that are used for the border) are from the TTkCfg.theme.
  2. (colors, chars, etc. may depend on widget-state; method instead of pure values) Becasue laf-objects are objects (and not only stored color values) this allows for the ability to have methods in an laf-object that "computes" colors/chars taking the current state of the widget into account. This is the part that I used in the progressbar-WIP (often people want colors to depend on the "progress": one uses a progress bar to show how full something is and want to colorize the event, that this thing is nearly empty, below 10%, with some red color, etc.).
  3. (feel-parameters and feel-functions) But wait, there is more ... [in this commercial spot] :-) Why do I call this look_and_feel and not style? Let's make an example. Think of a PushButton (not a TTkButton) that can be pressed by a keyEvent (e.g. space or return) and shows it was pressed (via key) for some time and then goes automatically back in the unpressed state. This timing may be controlled by an attribute of an laf-object. So the keyEvent sets the pressed state and triggers an update and also starts a Timer to "unpress" the button in the near future. That's not a style or color but that is a "responsive" feeling.

Pros: less slots per widget; laf-object may "compute" the look-and-feel depending on the state of widget; more parameters possible (without using slots); no additional methods needed at the widgets to prescribe special look-and-feel (e.g. setBorderColor or the missing setTitleColor or setTitleAlign, etc.) for a widget.

Cons: one level of "indirection" (the "laf-Proxy") more at every paint-Event.

[Sorry for the long text.]

@ceccopierangiolieugenio ceccopierangiolieugenio added enhancement New feature or request question Further information is requested labels Dec 11, 2022
@ceccopierangiolieugenio
Copy link
Owner

Simple answer about 1,2,3,4
there is a little bit of a mess because when I started the project There was no "theme" class and I couldn't predict the complexity derived from having this attributes in the widget itself, this is the reason why the first widgets (Label, Button, Frame and few more) allows you to change those attributes during the initialisation and through some methods,
same if you noticed about the drawing routine, later widgets draw everything in the "paint()" using canvas primitives, older widgets rely in some ultra comlpex specialised methods in the canvas object itself.

After a while I decided to rely mainly on the themes and I didn't modify the old widget to comply with it.
Same happened for TTkString, I added this class not long ago and I haven't backported all the old widgets to convert the strings to TTkString.

So far I would like to get rid of all the "style" attributes if possible and use a better way to handle them.
I used some hacks here and there to quickly achieve some goals, i.e. the way I abuse "setBorderColor" in the tabwidget to have a uniform color when highlighted.
I think the look_and_feel (TTkLookAndFeel?) object may works, maybe any object should have its own LAF derived class to expose all the api required to customize it.

@luchr
Copy link
Contributor Author

luchr commented Dec 12, 2022

I think the look_and_feel (TTkLookAndFeel?) object may works, maybe any object should have its own LAF derived class to expose all the api required to customize it.

This is a great idea. Then things are decentralized and can be optimized:

Let's make the assumptions

  • paintEvent occurs often
  • laf-changes are rare

Then there can be one "basis laf" which provides generic laf-hints: a few window backgrounds (modal, non-modal, popup, warning, etc.); a few typical border colors (active border, inactive border) a few text-colors etc. And this basis laf is using the Event-Signal-Mechanism to inform all "listeners"/slots when something has changed.

Every Widget can have a specialized laf class which saves a reference to the "basis laf" and uses the generic hints of the "basis laf" to precompute once many/all of the needed colors, chars, ... laf-parameters, etc. The computation may be expensive, but it's only called very rarely in the event of the construction of the specialized laf-object and if the event "there are changes in the basis laf" occurs. In the paintEvent the widget can use its specialized/own laf-instance to query for the laf-parameters (or to call laf-functions).

[Every specialized laf-object has a API so that it's possible to overrule the default colors-, chars-, laf-parameters- computations.]

Then the only "Con" point above has vanished. During a paintEvent there is no indirection and only the (pre-)computed return values of the widget's laf-object are used.

@luchr
Copy link
Contributor Author

luchr commented Dec 17, 2022

Hello,
just a few additional remarks. Looking at the PR #75 and for example the current state of TTkLookAndFeelPBar it looks like we are converging to an (very old) concept: UI delegates, like in Java's swing framework.

In the concrete progress bar example:

  • An other background character (instead of space), like ██████░░░░░░░
  • A color gradient for the bar
  • A little spinner at the current position: ██████░░░░░░░ => ██████-░░░░░░ => ██████\░░░░░░ => ██████|░░░░░░ => ██████/░░░░░░ => ███████░░░░░░
  • Text on the right, on the left or inside the bar
  • etc., etc.

One cannot write one paintEvent method having all the cases in mind. In the end of this direction, the paintEvent method is delegated to a look-and-feel "UI". And every look-and-feel(-implementation) has its own way/design.

What do you think about this direction?

@nickandwolf
Copy link

I just want to pop on and say I would love to specify border, background, default text colors with escape characters to manage in-line colorization. I'm big in UI and readability and this would be cool. Thank you!

@luchr
Copy link
Contributor Author

luchr commented Dec 20, 2022

@nickandwolf I'm not sure if I understand what you mean. I understand that if there are some "labels" or some "paragraphs" in the UI, that one might want to have the possibility that some of the words (inside the paragraphs/labels) are red, and some others are green, to make everything more readable. This might be done with inline ANSI-Escape sequences (or other ways).
But what type of "in-line colorization" do you have in mind for borders and/or backgrounds. Typically the width/height of the objects that have borders change and so the borders must be computed dynamically. One can think of giving the possibility for some color gradient thingy (please see picture for an example). But all of this cannot be given "in-line" in a "border string". Because every part of the border gets longer or smaller if the object is resized. Same argument for the background ...

20221220_021149

@ceccopierangiolieugenio
Copy link
Owner

ceccopierangiolieugenio commented Dec 20, 2022

@luchr - #issuecomment-1356381197

Hello, just a few additional remarks. Looking at the PR #75 and for example the current state of TTkLookAndFeelPBar it looks like we are converging to an (very old) concept: UI delegates, like in Java's swing framework.

In the concrete progress bar example:

  • An other background character (instead of space), like ██████░░░░░░░
  • A color gradient for the bar
  • A little spinner at the current position: ██████░░░░░░░ => ██████-░░░░░░ => ██████\░░░░░░ => ██████|░░░░░░ => ██████/░░░░░░ => ███████░░░░░░
  • Text on the right, on the left or inside the bar
  • etc., etc.

One cannot write one paintEvent method having all the cases in mind. In the end of this direction, the paintEvent method is delegated to a look-and-feel "UI". And every look-and-feel(-implementation) has its own way/design.

What do you think about this direction?

I always tried to keep everything simple without adding too many edge cases for customisation and keeping a solid standard behaviour/look. (for this reason I mimic the way QT works/looks)
I agree that we cannot write a paintEvent covering all the possible cases, but at the same time I don't want to have a paint event that just forward everything to the lookAndFeel or some other callbacks to allow any possible customisation,
for this reason in some cases (i.e. tab buttons) I extended the class rewriting the paint event to provide the look I wanted to achieve.

And unfortunately I am still stuck with the old way UI concept,
I never managed to fully understand modern UI frameworks,
especially the amount of code, files, abstraction and libraries you need to implement in order to display some basic controls.

@ceccopierangiolieugenio
Copy link
Owner

ceccopierangiolieugenio commented Dec 20, 2022

@nickandwolf - #issuecomment-1358515339

I just want to pop on and say I would love to specify border, background, default text colors with escape characters to manage in-line colorization. I'm big in UI and readability and this would be cool. Thank you!

can you provide some pictures of your idea?

About the gradient,
I was prototyping long ago the color modifier feature to add dynamic color effects automatically.

class TTkColorGradient(_TTkColorModifier):

i.e.

pyTermTk/demo/ttkode.py

Lines 175 to 180 in f82541b

kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#008800", modifier=TTkColorGradient(increment=-6)), title="uno"),"uno")
kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#880000", modifier=TTkColorGradient(increment=-6)), title="due"),"due")
kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#000088", modifier=TTkColorGradient(increment=-6)), title="tre"),"tre")
kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#888800", modifier=TTkColorGradient(increment=-6)), title="quattro"),"quattro")
kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#008888", modifier=TTkColorGradient(increment=-6)), title="cinque"),"cinque")
kt.addTab(_KolorFrame(fillColor=TTkColor.bg("#880088", modifier=TTkColorGradient(increment=-6)), title="sei"),"sei")

where the gradient is a property of the color itself and the widget does not need to implement any extra routine in the paint method:
image

Unfortunately I never investigate all the implications and possibilities of this feature and so far only a vertical gradient is available.

@nickandwolf
Copy link

nickandwolf commented Dec 20, 2022

@luchr I love that mockup image. Unfortunately I am greatly limited most of the day and cannot create a mockup but that image is amazing.

So I look at Tkinter and video game UI frameworks when it comes to styling. In my current project, I'm finding the information looks cluttered and hard to read despite it being organizationally sound. Here are some of my thoughts.

  • Border styles, ASCII and ANSI offer a lot of built in GUI doodle styles. Single line, double line, single blocks to make thick lines, one can use the bullet icon • to create dotted borders, etc. Then if we can apply a color or gradient to that border style. If half-step blocks can be used to tighten to colors, awesome. A way to dynamically distribute gradients is by defining the starting point and ending point colors and stepping there dynamically OR having the user define the steps so the gradient isn't super jarring.
  • In-Line styling, this is just like adding the & and escape characters, but with more options crammed in. Strike-through, bold, underline, italics, size (if possible), background color, foreground color, and a gradient can be defined there as well. Using an existing markup language, a quick framework can be made with a clear path. So if I have a title of a frame that says, "All the Cool Stuff", I could change it by going "[b]All[/b] the [i]Cool[/i] [u]Stuff[/u]" (but this would require us to parse or use escape characters for the tags, "/[b]All/[/b] the /[i]Cool/[/i] /[u]Stuff/[/u]", so it may be better to use {} since those are basically never used in actual text). For colors it could be [fg=0x00000]Test[/fg=0xFFFFFF] to create a gradient, same with background. Leave out the fg= on the last tag (to just [/fg]) to maintain the color throughout.
  • Titles, having something other than | to separate titles. Like a box or dotted thing or something so sub-frames hold different weight than the main frame.

I do not know exactly how your engine manages text but if it has the ability to render multiple characters on top of each other (like BearLibTerminal), it can allow a greater diversity of stylings.

Many TUIs I'm looking at are not as dynamic as yours @ceccopierangiolieugenio while providing mouse support. You mockup image and code is also very close to what I think the next step is to add stylization. I'm leveraging that to make character sheets and character creators. My current project is for Cyberpunk 2020 and could use more diversity to make sections of it pop. It would also benefit from having a large text renderer (like in your demo program that shows mouse coords).

image

I am basing this off a PDF character sheet but if you look up 5E D&D character sheets, you can see how the layout is a real test for this library.

EDIT: Originally on my Stats block, I had a label from the host frame over the INT etc frames but when I click on the spinnerbox inside, it covered my label. Making frame focus toggleable would alleviate that when it comes to be stupid fancy.

@ceccopierangiolieugenio
Copy link
Owner

Experimenting with the gradient modifier:

Peek.2022-12-21.09-22.webm

@ceccopierangiolieugenio
Copy link
Owner

ceccopierangiolieugenio commented Dec 21, 2022

@nickandwolf
Unfortunately there is no way to include tiles or change the font size, what my library produce are the ANSI codes and UTF-8 chars interpreted by the terminal, so any graphical feature is restricted by what the terminal is capable of.
(There are some terminals that support images and/or tiles but this is outside the scope of my project, my final goal is to have a terminal UI that may works via ssh/telnet or serial)

Anyway,
about the half-step blocks
I am already using them in the rasterizer. in this case you have the restriction of 2 colors (bg/fg) per char/tile:

image

# 0x00 0x01 0x02 0x03
quad = [ ' ', '▘', '▝', '▀',
# 0x04 0x05 0x06 0x07
'▖', '▌', '▞', '▛',
# 0x08 0x09 0x0A 0x0B
'▗', '▚', '▐', '▜',
# 0x0C 0x0D 0x0E 0x0F
'▄', '▙', '▟', '█']

or in the big font mouse/key input viewer:

image

"▗▄ ",
"▙▄▌",
"▌ ▌",
],
'B':[
"▄▄ ",
"▙▄▘",
"▙▄▘",
],

I am actually thinking also to support the sextels in order to have 6 blocks resolution per char.

@Metallicow
Copy link

@ceccopierangiolieugenio Haha just saw this repo. Your result is absolutely hilarious. 🤣 When I first started writing my source code editor I had a goofy Idea to do something like this. I use wxPython and Scintilla tho. I think I managed to get A frame, some checkboxes and a couple buttons done in a windowless wxFrame housing a wxSTC editor for the drawing, but nothing like this. I at some point kinda just laughed at it and thought who would ever take this seriously if they changed the font in the editor, plus handling all the absolute positioning coordinates kinda overwhelmed me in the code... Well you seem to be the first to try and tackle a GUI Term/Console style...

My attempt turned out a bit like your AboutBox but without the graphics. Just A title, short description, 3 checkboxes and Yes/No buttons... Nothing to brag about tho. LOL. If I even come across my ol sample I'll have to relook into this. Maybe I could get it working right... haha
Any thoughts on possibly expanding to other toolkits? Just curious...

@ceccopierangiolieugenio
Copy link
Owner

@ceccopierangiolieugenio Haha just saw this repo. Your result is absolutely hilarious. 🤣 When I first started writing my source code editor I had a goofy Idea to do something like this. I use wxPython and Scintilla tho. I think I managed to get A frame, some checkboxes and a couple buttons done in a windowless wxFrame housing a wxSTC editor for the drawing, but nothing like this. I at some point kinda just laughed at it and thought who would ever take this seriously if they changed the font in the editor, plus handling all the absolute positioning coordinates kinda overwhelmed me in the code... Well you seem to be the first to try and tackle a GUI Term/Console style...

My attempt turned out a bit like your AboutBox but without the graphics. Just A title, short description, 3 checkboxes and Yes/No buttons... Nothing to brag about tho. LOL. If I even come across my ol sample I'll have to relook into this. Maybe I could get it working right... haha Any thoughts on possibly expanding to other toolkits? Just curious...

Yes, which toolkits have you in mind?

@Metallicow
Copy link

Metallicow commented Apr 28, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants