News:

Simutrans.com Portal
Our Simutrans site. You can find everything about Simutrans from here.

[Project] GUI Theme

Started by Max-Max, May 31, 2013, 11:12:48 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Max-Max

Project description
This 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.

The project documentation can be read in this PDF.

Examples of suggested themed components

  • Window title borders, title background and gadgets
  • Windows border (other than title), backround and resize icon
  • Button,checkbox,radio button,scrollbars, arrow buttons
  • Edit control (border, background)
  • Frame border (to group stuff), separator
  • System colors, such as, text, highlight, shadow
There will be a fallback theme for all GUI items.

Example of suggested resize capabilities
A 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).

Developer team
Max-Max (Max Kielland)

Drop a line if you are interested in joining the team.

Snapshot of work in progress

Progress
Buttons 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 documentation
This document is a draft in progress. It has information for both coders and artists.
- My code doesn't have bugs. It develops random features...

jamespetts

The snapshot seems to be set to private.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

Max-Max

Sorry for that, the permission should be public now.
- My code doesn't have bugs. It develops random features...

jamespetts

Yes, does work. Those larger buttons do look quite pleasing; but I wonder how this would work with some of the larger dialogues (finance?) or with lots of dialogues on screen?
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

Max-Max

These large buttons are only a test case to see that everything is coded right. The size of the buttons will be determined by the theme later on.

With some advance resize features we could even have the dialogue to rearrange them better if the client area gets too small.
We are only at the first step so far...
- My code doesn't have bugs. It develops random features...

Markohs

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.

ӔO

good luck with the project!

the current gui is definitely in need of improvement.
My Sketchup open project sources
various projects rolled up: http://dl.dropbox.com/u/17111233/Roll_up.rar

Colour safe chart:

Max-Max

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.
- My code doesn't have bugs. It develops random features...

IgorEliezer

Pretty cool. :)

I sometimes think how it would look the title bars with bigger fonts. What if the current fonts were used, but scaled up 1.5x or 2x?

Fabio

I find splendid you picked this project.
GUI needs some love and most in current dev team don't like GUI coding much.

:thumbsup:

Max-Max

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...
- My code doesn't have bugs. It develops random features...

TurfIt

The building blocks of your requested members are already present. display_calc_proportional_string_len_width() * large_font_total_height gives the bounding box.

prissi

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.

Max-Max

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.

// gadget size (replace with real values from the skin images)
#define D_GADGET_SIZE D_TITLEBAR_HEIGHT

// Arrow size (replace with real values from the skin images)
#define D_ARROW_WIDTH  (10)
#define D_ARROW_HEIGHT (10)

// Scrollbar params (replace with real values from the skin images)
#define KNOB_SIZE    (32)

// Vertical divider element height (replace with real values from the skin images)
#define D_DIVIDER_HEIGHT (D_V_SPACE)

// Tooltip position (replace with real values from the theme)
#define TOOLTIP_MOUSE_OFFSET_X (16)
#define TOOLTIP_MOUSE_OFFSET_Y (12)


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...
- My code doesn't have bugs. It develops random features...

Max-Max

Quotedisplay_proportional_... returns the length of a text in pixel
Okay I will take a look at these functions.

QuoteBut since the buttons are skinned BUTTON_HEIGHT is taken directly from the skin images.
I guess you mean D_BUTTON_HEIGHT and D_BUTTON_WIDTH, yes I use them already.

Quote(Btw use KOORD_VAL for coordinates, to be ready to switch to whatever size we may need next.)
Okay, so you want me to use koord(KOORD_VAL,KOORD_VAL). While I'm at it, why not rename and use COORD_VAL instead? KOORD_VAL seems to be pretty safe to rename...

Quote...and LINESPACE (which were obviously introduced for this purpose).
Already using that one and even centre it around each complementary edit control.

The divider is also centred inside its client area, defined by the theme.
- My code doesn't have bugs. It develops random features...

TurfIt

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.

Max-Max

There are many places where a smaller magic number is used instead of D_V_SPACE and D_H_SPACE. In many cases a magic number is used instead of D_MARGIN_XXXX that differs a few pixels.

Some places use a magic number, LINESPACE, D_V_SPACE or D_BUTTON_HEIGHT for dividers, I replace all these with the new D_DIVIDER_HEIGHT.

The difference isn't in the 100 pixel range, it is a few pixels here and there...

My screen shots are only test cases to see that I have covered everything, not the final and only result. All these relations can be controlled by the theme...
- My code doesn't have bugs. It develops random features...

Max-Max

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?
- My code doesn't have bugs. It develops random features...

TurfIt

??. gui_label_t is used in many dialogues: halt_detail, money_frame, detail_frame, ...

Max-Max

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.
- My code doesn't have bugs. It develops random features...

prissi

Just a few things to remember:

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.

Max-Max

Prissi, you have done a good job so far :)

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...
- My code doesn't have bugs. It develops random features...

prissi

Imho please focus on scaleable dialogues. Do not try to do to much at the same time, i.e. introducing new controls and so on, or this project will be less likely to find a good ending.

Max-Max

I'm sticking to my roadmap, still focusing on replacing magic numbers with defines and already available classes.
- My code doesn't have bugs. It develops random features...

Markohs

 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.

Max-Max

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.
- My code doesn't have bugs. It develops random features...

Max-Max

#26
Another thought...

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.
- My code doesn't have bugs. It develops random features...

Markohs

Quote from: Max-Max on June 03, 2013, 01:21:04 PM
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. ;)

Quote from: Max-Max on June 03, 2013, 01:21:04 PM

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.

Quote from: Max-Max on June 03, 2013, 01:29:33 PM
Another thought...

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. ;)

Max-Max

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

An_dz is working on Item 2.
- My code doesn't have bugs. It develops random features...

Markohs

okay. I'd say after phase 1&2 are finished,  you should post a patch here,  we can discuss a bit over it and we commit it to svn.

Markohs

Quote from: Max-Max on June 03, 2013, 02:39:18 PM
I leave shadow text as direct draw, but it should, in my opinion, be implemented in the label class later on (not now)...

btw, is this hard to do? Because you can maybe move it to phase 1, shouldn't be too hard to implement, no? No idea, as you wish. ;)

Max-Max

I did move it in once but it got a little bit more than "just move" if we are to support other than LEFT alignment with shadowed text...
...so I removed it again to deal with it later.
- My code doesn't have bugs. It develops random features...

Markohs


prissi

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.

Max-Max

#34
Prissi

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.

but I did noticed this from the previous version:

gui_scrolled_list.cc Line 236

      switch(type) {
        case list:
          break;
        case select:
          display_vline_wh(x, y+1, h-1, MN_GREY0, true);
          display_fillbox_wh(x,y,w,1, MN_GREY0, true);
          display_vline_wh(x+w-1, y+1, h-2, MN_GREY4, true);
          display_fillbox_wh(x+1,y+h-1,w-1,1, MN_GREY4, true);
          display_fillbox_wh(x+1,y+1,w-2,h-2, MN_GREY3, true);
          break;
      }
   
236  display_fillbox_wh(x,y,w,h, MN_GREY3, true);
237  display_ddd_box(x,y-1,w,h+2, COL_BLACK, COL_WHITE, true);

      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.
- My code doesn't have bugs. It develops random features...