Author
Topic: [Project] GUI Theme (Read 129608 times)

Project descriptionThis project's goal is to implement user created themes into Simutrans' GUI with resize capabilities.We will also see if we can use the theme system to also cover themes targeting portable devices.

Example of suggested resize capabilitiesA window can be constraint to a minimum and/or maximum size.

When a window's client area gets to a point where its client area can't fit all child controls, it will automatically start to optimize/collapse the child controls. This is a suggested chain of reactions.

Make paddings smaller until they are 1px

Make Dividers smaller until they are their minimum graphical representation

D_H_SPACE and D_V_SPACE smaller until they are 1px

Remove dividers

Collapse expanded group boxes

Make buttons smaller (width) to the containing text width

Remove text from buttons with an icon associated, only show the icon

make window area scrollable

Each control type will have a minimum size, defined by the theme. This to ensure that buttons, edit fields, etc. will always be accessible on a portable device.

Road map

Convert all GUI elements to use the elements defined in frame_t.h instead of all the magic numbers. This may result in additional element definitions.

Set each element size to the size of its skin image.

Create a true window class, gadget bar class and a gadget class

Introduce a true client relation and update all dialogs

Modify/add controls if needed to work better with the concept of resizing

Create a dialog to select and apply a skin (theme).

Add detection and special handling for a small screen area (example portable devices).

ProgressButtons are not bound to use the same size as the their image. You are no longer restricted to use the hard coded sizes for skin button elements (well, as long they fit in the PAK cell size). The default button size can be overridden in the theme.tab file. Button images are split into 9 images so a button can be drawn in any size with the same graphical look.text and system colours, such as highlight, shadow, face etc can be configured in the new theme.tab file.

Patches:So far the patches has all gone into the main trunk already.

Project documentationThis document is a draft in progress. It has information for both coders and artists.

I'm glad someone is in this project, I really hope it's sucessful and it brings order to the GUI, that's a bit cryptic part of the code, just because all of this hardcoded values splattered all across the code.

Well, GUIs is one of my little passions and I have written some customized controls to Windows in may days and another GUI systems for LCD displays in embedded projects

The basic design of it is here is quite good with a light weight framework. However I do miss the client area concept and that the various buttons should probably be in their own classes to keep a true object oriented design.

A back link from each control to its parent (owner) would be desirable too, especially if we want the to reorder stuff when the client area is growing/shrinking.

With the concept of a true client area, a container can change parent and all its child control will follow in the same relative position, to the new parent's client area.

I don't want to mess around to much in the first phase. I will probably do it a little further up on the road map.

If someone wants to help we could need a rect_t class (similar to Windows RECT).

class RECT {

public: sint32 left; sint32 top; sint32 width; sint32 height;

};

and some basic operator to go with it...This will mainly be used to define a client area within each control.

Regarding fonts, I have only found one class reading a font and a bunch of helper functions to draw text.

One thought would be to expand the system with a logic font class derived from the current that transforme a font to create Bold, Italic, double size, half size etc... If the logic font takes a font class as a parameter it will be possible to transform a font in multiple steps.I guess this is an entirely project on its own... fell free to pick that one up

Already now, I need some new font class members:

Returns the bounding box of a string if it was drawn on screen. This would be a typical member of the font class.

Returns how many characters in a string will fit inside a provided box.

Returns the size of the widest character in a string of chars.

If some one would be willing to implement them I would be very happy...

display_proportional_... returns the length of a text in pixel and LINESPACE the height. But since the buttons are skinned BUTTON_HEIGHT is taken directly from the skin images. (Btw use KOORD_VAL for coordinates, to be ready to switch to whatever size we may need next.)

In order to keep contribution for outsiders possible, I would rather not introduce to much different fonts. Maybe if really needed two fonts, a large and a small one for tooltips or infoscreens (both could be identical for normal machines).

As said I started work on the list windows/filter frame and line window, so no need to touch those. They will only use the variables from gui_frame_t and button_t and LINESPACE (which were obviously introduced for this purpose).

Which variables did you add? You just show a screenshot but no code? Could you show a diff?

Offtopic:Actually, what simutrans really could use by now are an unified statistics class, which shows the gui_component, has a statistics for all the last 48 game months and all game years since start, and can export these data to CSV. But that is an entirely different project.

I'm removing all magic numbers and replacing them with the defines in frame_t.

To make all dialogues consistent, it means that some of their controls will move slightly a bit because I use the D_V_SPACE and D_H_SPACE between them. Some classes has only magic numbers and some are already updated correctly and don't need any updates, but most of them are a mix.

I'm also replacing dividers drawn directly with the class divider_t.There are a number of "controls" that are drawn directly, instead of being its own class. I plan to convert them into classes.

Yes, some dialogues will increase slightly in size, but I have a pretty cool plan on how to make them auto-collapse in several levels when the client area becomes to small. To do this we need to use the concept of a client area.

The work has just began and I don't see any point in posting a diff already?

These has been added and will be replaced by proper variables in the theme system.

There are many defines in frame_t that doesn't belong there at all, like the button size. I think all these kind of variables should be in a theme class instead. Remember this is work in progress and there might be more or less elements...

When it comes to fonts, well it was just an idea and it is really up to the theme designer what fonts to use. But we are not touching that area until the themes are done. The creation of logical fonts is just a neat way to create variations of an existing font, without having additional fonts added to the font directory. These only exist in memory during runtime, but we deal with this later.

Another cool thing for theme designers would be to draw their on fonts in a sprite sheet and then have them "compiled" into a theme font (or a BDF,FNT,whatever...)

- For every obstacle, I see a challenge and for each challenge I see an opportunity...

If replacing the magic numbers with a define results in things moving, then IMHO the wrong define was used...I'm sure the current list of defines is not sufficient to fully replicate the structure of the existing dialogs; More need to be added.

There is a Label control (gui_label_t) that isn't used at all.I suspect the thought was to replace all direct drawn text labels with this label class. It takes care of both translations, tooltip, text colours, text justification and some text formatting.

Was this the intention? Shall I replace all direct drawn texts labels (not text inside other controls, such as buttons, edits etc...) with this control?

Yes, you are right I had the wrong filter when I searched for it.But there are a many dialogues that do not use it, so I guess I should replace all these direct draw with the label class instead.

The more I dig into this, the more it stands clear. The simWin and frame_t needs to be rearranged to get a more defined role. frame_t has nothing to do with a windows title and gadget bar. Now it leaves space for it at the top, bot do not handle it.

The result of this is that if you initialises controls in a frame_t constructor you need to take the title bar into account, but in the draw routine you should not.

The frame_t should operate without the knowledge of a title bar and leave that part to the simwin. Simwin should position the frame_t right depending on a visible title bar or not. With this concept the frame_t becomes a true client area for simwin and all controls are positioned the same in both constructor and redraw routines.

frame_t positions a container within the D_MARGIN_XXXX margins and now you don't need to worry about the margins at all when positioning controls. All controls will be relative to the frame_t's client area (container).

You can easily minimize a window by simply not drawing the frame_t from simwin, only showing the title bar at the bottom of the screen.

Originally simutrans had three! different dialogue classes. It took me three month to unify them to the one (well, the tool window are still somewhat different). Also the container does not care about the title bar. Therefore I left the offset. But you are right: Removing the titlebar offset completely is something that should be done when one anyway touches all dialogues. One way woudl be to use two constants, one for the current code which draws the actual bar and one which is set to zero when everything else is in place. (Also iron way has skinable title bars; maybe we shoudl think about this too?)

Another outcome of the different dialogue classes were the handling of redraws. Many dialogues had text drawn themselves, while other used labels. When using labels would allow me to get rid of zeichen() completely, I did the conversion (at least for most of them), while I left others as they were.

The D_MARGIN_XXXX are very recent. Dialogues using these should be fully scaleable (or there has been errors in implementation). The other will use mostly offsets of either 1, 4, or 10 pixel.

In some cases (Depot) even though there might be a margin defined, the vehicle lists should use no margin at all, to squeeze as much evhicles in the depot as possible. Also scroll areas should have there scrollbar at the right. Thus using an offsetted continer for this will not work.

NB: Simutrans dialogues try to get intially a single width (and height). This fails (in the moment) for the line dialogue, finances, and some option frames. The idea is to have tileable dialogues.

I started to dig into it, but came to the conclusion that I will continue my current work first. I had it almost to work, but there is a problem in banner_t where it overloads has_title(), but the original frame_t version is called instead. This resulted in the button detection being a D_TITLEHIGHT offset wrong. I will deal with this later.

Yes, when it comes to themes, I plan to not only to include the window's gadgets and title bar, but also the window frame, tab pages, bevels and a new control; collapsible groupbox.

I'm trying to think of portable devices as well with auto-collaps in several levels so the windows may fit a smaller screen. But this is further up the pipe...

You should plan this in incremental steps in my oppinion. I'd say each of the milestones should be able to be submitted to subvrrsion..

The idea after this is making possible to get results committed to the game even if the project ends not being completed. just plan one achiveable close and complete goal. we'll evaluate and refine it and incorporate to the game. once done, tou can for the next goal.

This is what I do. I stick to the road map. If you guys think this is to big steps, I can create patches after each dialogue but since I'm going a bit forth and back in how things are implemented it would sometimes be more extra work for you guys to implement the same things while I changes some implementations...

Let me know how often you want me to send patches...

Another thought..I noticed that the painting (zeichnen) is called every frame, even if the dialogue hasn't change. One thought would be to at the end of zeichnen save the dialogue as an off screen image.

Next time, if the dialogue hasn't change, paint the off screen image instead. This also applies when moving a dialogue, just move the off screen image instead.

As soon the dialogue is marked dirty, call zeichnen again to to get a new off screen image. This would speed up the frame time quite a bit.

All resize stuff, aligning controls to an edge or stretching, is done in zeichnen. This calculations are only needed when the client area is resized.

Normaly a GUI system has a resize event handler that takes care of this, aligning controls. This will reduce the time spent in zeichnen and speed up the frame time a little bit more.

*** EDIT ***To clarify the difference between set_groesse() and a resize event handler are the following:set_groesse sets a controls size where a resize handler notifies all child controls that the client area has changed in size/position.

A notification takes out the alignment handling from the set_groesse() function. For example you could set a control to bottom and/or right alignment. Whenever it is notified that the parent's client area has changed, the control can adjust itself.

The notification will bubble down from set_groesse() to all child controls.

This is what I do. I stick to the road map. If you guys think this is to big steps, I can create patches after each dialogue but since I'm going a bit forth and back in how things are implemented it would sometimes be more extra work for you guys to implement the same things while I changes some implementations...

Let me know how often you want me to send patches...

I'd say a good moment to submit a patch here will be when you have replaced all the macro usage and dialog hardcoded values by variables, on all dialogues. I'd focus that first, just defining a set of default values for that variables, a "default" theme. It must be a patch that's ready for inclussion on the trunk code, that's expected to contain a few minor bugs (that's work-in-progress quality, ready for nightly and deveoper testing).

Additional modification of dialogs (i.e. making them scalable or modifying the class hierarchy too much) should be done in a posterior iteration. I'd do it that way, but ofc, that's just my oppinion.

Another thought..I noticed that the painting (zeichnen) is called every frame, even if the dialogue hasn't change. One thought would be to at the end of zeichnen save the dialogue as an off screen image.

Next time, if the dialogue hasn't change, paint the off screen image instead. This also applies when moving a dialogue, just move the off screen image instead.

As soon the dialogue is marked dirty, call zeichnen again to to get a new off screen image. This would speed up the frame time quite a bit.

See, you are about to grow the snowball here, and wide the amount of work you need to do. It's not a bad idea, but I don't personally think it's worth it, the perfromance increase whould be too low to justify this change, and adds complexity and mantainability problems. I'd refrain to do this, at least for now. If you want to experiment with this, I'd add as a project item, but leave it for later development iterations.

All resize stuff, aligning controls to an edge or stretching, is done in zeichnen. This calculations are only needed when the client area is resized.

Normaly a GUI system has a resize event handler that takes care of this, aligning controls. This will reduce the time spent in zeichnen and speed up the frame time a little bit more....

I think this is a better idea, and it might be really worth implementing (whould be a "cache" of pre-calculated dialog vaules), but again, I'd pospone this for later. Take into caccount that GUI drawing it's *not* a performance problem in simutrans now, no need to code too much here, all changes should be focused on readibility, modularity, flexibility and mantainability of the GUI, performance is not critical.

I'd say making pixmap-skinnable title bars or implementing partial transparencies it way more interesting to your project thatn this.

Better to work and implement on things our players will be able to see and interact with, that teorical performance increases that might not be enough to make a difference.

But ofc, all things I expressed so far are my thoughts, it's your project, do it as you wish.

My intention wasn't to do it myself right now, I stick to the road map.

I'm not comfy enough with the underlying graphic functions to be able to create an off screen map on the fly, but it should be fairly simple for someone comfy with it.

A resize handler routine relies on the client area concept and I can do it when I get to the resize part further up the road map.

I'm still on item 1 in the road map replacing magic numbers and direct drawn text and dividers with classes.I leave shadow text as direct draw, but it should, in my opinion, be implemented in the label class later on (not now)...

About zeichnen:Very few (only the very old) dialogues do calculation of positions on the fly. But for most stuff, it does not matter, as teh height of a line of text is anyway known, and the extra addition may be even faster than accessing memory.

About dirty stuff: Most dialogues contain a world windows, which is always dirty, and many contain some text (liek load o,porduction, statstics, text) which is frequently dirty. Very few dialogues are actually static. I am not sure that you could really save a lot by buffering the content. But such a patch would be independent from the resizing, so go ahead.

There is also a divider class, gui_divider_t Please go through the gui/components folder. YOu might find more surprises there

I would even suggest to submit dialogue by dialogue. That way one would have a chance at discussion, and (imho) this is also more rewarding to see thing actually in the game.

I have merged the update from yesterday into my branch and it still works

How ever, I have made quite some rework on the tab control so it will draw its children in the client area below the tabs. The various elements of the tabs are parametric through defines. This to prepare it for themes. I will ask you to merge in my tab control to not interfere to much with my ongoing work.

There is one noticeable side effect. The client area of a tab was invisible before, now it defines the boundary. You will notice in some dialogues how the tab control isn't resized with the window and some lines may continue outside when resized.

Due to the fact that a tab's children are relative to the tab's client area, starting right under the tabs, all children are shifted down a bit because the old tab control didn't adjust for the tab area.

Another thing.I was messing with the gui_scrolled_list because it was drawing outside its own bounding box. I noticed that you have began to do the same rework I had in mind too I will drop my changes until you are done with yours.

PUSH_CLIP(x+1,y+1,w-2,h-2);In the previous switch case the bounding box is drawn depending on the list type, but on line 236 it is always overwritten by a new bounding box starting outside the controls bounding box. This gives miss alignment when positioning the control.

I suggest to remove line 236 and 237, or move them into a default section in the switch case and keeping them inside the bounding box.

switch(type) { case list: break; case select: display_fillbox_wh(x+1,y+1,w-2,h-2, MN_GREY3, true); display_ddd_box(x,y,w,h,MN_GREY0,MN_GREY4,true); break; default: display_fillbox_wh(x+1,y+1,w-2,h-2, MN_GREY3, true); display_ddd_box(x,y,w,h,COL_BLACK,COL_WHITE, true); }May I also suggest that you create a list item class that is pure virtual and then derive one for a list item. In this way we can mix different type of items in the same list.