ceccopierangiolieugenio / pyTermTk

Python Terminal Toolkit - a Spiced Up TUI Library 🌢️
https://ceccopierangiolieugenio.github.io/pyTermTk/
MIT License
585 stars 22 forks source link

Discussion: Themes vs Styles vs LookAndFeel #77

Open luchr opened 1 year ago

luchr commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

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 commented 1 year ago

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:

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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

@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 commented 1 year ago

@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. https://github.com/ceccopierangiolieugenio/pyTermTk/blob/f82541b16ec04654e2716f4a81a42599ac128118/TermTk/TTkCore/color.py#L186

i.e. https://github.com/ceccopierangiolieugenio/pyTermTk/blob/f82541b16ec04654e2716f4a81a42599ac128118/demo/ttkode.py#L175-L180 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 commented 1 year ago

@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.

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 commented 1 year ago

Experimenting with the gradient modifier:

Peek 2022-12-21 09-22.webm

ceccopierangiolieugenio commented 1 year ago

@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

https://github.com/ceccopierangiolieugenio/pyTermTk/blob/789128d740500d29dd5a9a5790b06c337baf66bf/TermTk/TTkWidgets/image.py#L79-L86

or in the big font mouse/key input viewer:

image

https://github.com/ceccopierangiolieugenio/pyTermTk/blob/789128d740500d29dd5a9a5790b06c337baf66bf/TermTk/TTkTestWidgets/keypressviewfont.py#L202-L210

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

Metallicow commented 6 months ago

@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 commented 6 months ago

@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 commented 4 months ago

Oh, I dunno... wxpython, pyside/pyqt, tk, kivy, Scintilla. Mostly ones that have cfamily python bindings. I really don't do too much with html css java anymore besides docs. Vulkan/SDL2 would be overkill for something like this but would be awesome to see.

On Fri, Mar 8, 2024, 9:00 AM Pier CeccoPierangioliEugenio < @.***> wrote:

@ceccopierangiolieugenio https://github.com/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?

β€” Reply to this email directly, view it on GitHub https://github.com/ceccopierangiolieugenio/pyTermTk/issues/77#issuecomment-1985848430, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABDTXRGAANLXPKF6AHMJU5DYXHG7XAVCNFSM6AAAAAAS3H4DKCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSOBVHA2DQNBTGA . You are receiving this because you commented.Message ID: @.***>