PortMaster Theme Specification

PortMasters new gui has a flexible theme system build around a theme.json file.

This is currently a WIP documenting the theme format.

Scene Format

The theme has a few different sections:

It also uses a template system for the text, so that the gui elements automatically update as the text changes.

Element Inheritence Order:

The order that elemets get their values is defined as:

element -> scene #base -> global #base

If you use a reusable element the order is:

element -> element #element -> scene #base -> global #base

Element overrides

You can override values in elements based on hardware by specifying a hardware capabilities on an attribute:


    "#base": {
        "font": "DejaVuSans.ttf",
        "font-scale[hires]": 2.0,
        "font-scale[5:3,hires]": 3.0
    },

You can override entire elements based on hardware capabilities:


        "ports_list": {
            "area": [0.0, 0.1, 0.4, 0.95],
            "border-x": 16,
            "roundness": 10,
            "font-size": 14,
            "font-color": "list_text",
            "select-color": "list_selected",
            "text-clip": true,
            "text-wrap": false,
            "autoscroll": "slide",
            "scroll-speed": 30,
            "scroll-delay-start": 500,
            "scroll-delay-end":   500
        },
        "ports_list[hires]": {
            "area": [0.0, 0.1, 0.3, 0.95]
        },

Since this uses the capabilities system used in ports you can add them together and or not them.

Currently the capabilities are:

You can combine them like so:

Scenes

Scenes can define their own #base element, which will cause all elements in that scene to inherit from.

Currently there are the following scenes:

Scene: main_menu

This is the main menu scene, it requires the option_list element. It is the first scene to load and if backed out of will quit the program.

    "main_menu": {
        "option_list": {
            "area": [ 0.0, 0.0, 1.0, 1.0 ],
            "font": "DejaVuSans.ttf",
            "font-size": 10,
            "list": []
        },
        "button_bar": {
            "area": [ 0.0, 8.0, 1.0, 1.0 ],
            "font": "DejaVuSans.ttf",
            "font-size": 10,
            "bar": []
        }
    }

The option_list can contain whatever text you feel is most appropriate, the actual option that gets called is the same option in the option list.

Scene: options_menu

This is the options menu scene, it requires the option_list element. It is loaded from main-menu, this scene is reused for each subsequent option submenu.

    "options_menu": {
        "option_list": {
            "area": [ 0.0, 0.0, 1.0, 1.0 ],
            "font": "DejaVuSans.ttf",
            "font-size": 10,
            "list": []
        },
        "button_bar": {
            "area": [ 0.0, 8.0, 1.0, 1.0 ],
            "font": "DejaVuSans.ttf",
            "font-size": 10,
            "bar": []
        }
    }

Scene: ports_list

This is the main list of ports, it requires the ports_list element. It is loaded from main-menu, this scene is reused for listing ports for installing and uninstalling.

    "ports_list": {
        "ports_list": {
            "area": [ 0.0, 0.0, 1.0, 1.0 ],
            "font": "DejaVuSans.ttf",
            "font-size": 10,
            "list": []
        },
        "button_bar": {
            "area": [ 0.0, 8.0, 1.0, 1.0 ],
            "font": "DejaVuSans.ttf",
            "font-size": 10,
            "bar": []
        }
    }

Elements

The basics

Elements are always rectangles, they must always have an area parameter. This can be pixels specifically, or a percentage of the parent area.

If you specify negative pixels for the width/height it subtracts it from the width/height of the parent area.

If you specify the coordinates in pixels it is in thef format: x, y, width, height

If you specify it as a percentage of the parent area it is as: top-left-x, top-left-y, bottom-right-x, bottom-right-y


    "get_rect_perc": {
        // 25%, 25% to 75%, 75%
        "area": [0.25, 0.25, 0.75, 0.75]
    },

    "get_rect_px": {
        // same as above on a 640x480 display
        "area": [160, 120, 320, 240]
    },

    "get_rect_inset": {
        // same as above on a 640x480 display
        "area": [160, 120, -160, -120]
    },

As hinted to above, you can specify a parent element to use as the basis of the calculating element positions.

        "left_pane": {
            // [0, 0, 192, 480] on 640x480 display
            "area": [ 0.0, 0.0, 0.3, 1.0 ]
        },
        "right_pane": {
            // [192, 0, 448, 480] on 640x480 display
            "area": [ 0.3, 0.0, 1.0, 1.0 ]
        },
        "port_info_image_area": {
            // [202, 10, 236, 230] using the above right_pane as the basis
            "parent": "right_pane",
            "area": [ 10, 10, -10, 0.5 ]
        },
        "port_info_text_area": {
            // [202, 240, 236, 230] using the above right_pane as the basis
            "parent": "right_pane",
            "area": [ 10, 0.5, -10, -10 ]
        },

By mixing and matching these you can build very powerful layouts.

You can also use the element overrides to further customise for different screen sizes.

        "left_pane": {
            "area": [ 0.0, 0.0, 0.3, 1.0 ],
            "area[wide]": [ 0.0, 0.0, 0.4, 1.0 ]
        },
        "right_pane": {
            "area": [ 0.3, 0.0, 1.0, 1.0 ],
            "area[wide]": [ 0.4, 0.0, 1.0, 1.0 ]
        },

Elements can be themed by setting a fill , outline , thickness , and roundness :

Displaying Text

Currently there are a few ways of displaying text.

To display text at a minimum you need a font , font-size , font-color , and text .

    "text_element": {
        "area": [0.25, 0.25, 0.75, 0.75],
        "text": "Text to be displayed!",
        "font": "DejaVuSans.ttf",
        "font-size": 20,
        "font-color": [0, 0, 0]
    }

A special option is font-scale which should be used to broadly scale fonts across multiple elements or scenes using the element overrides.

    "#base": {
        "font-scale[hires]": 2.0,
    }

You can align text to different positions of the element.

You can control the way text is displayed with word wrapping, automatic scrolling, clipping, and scaling.

It also supports text auto-scrolling if it doesnt fit within the area it is displayed. It will horizontally scroll if the text is wider (word wrap is off), and vertically scroll if it is too tall (word-wrap is on).

    "element_name": {
        // Other element bits and bobs here.

        "text-clip": true,          // This must be true for scrolling to work
        "text-wrap": false,         // If text-wrap is false, it will default to a horizontal scroll, otherwise it defaults to a vertical scroll

        "autoscroll": "slide",      // null does nothing, "slide" scrolls down then resets, "marquee" scrolls back and forth

        "scroll-speed": 30,         // How many miliseconds between each scrolling step
        "scroll-delay-start": 500,  // How many miliseconds to wait before starting to scroll
        "scroll-delay-end":   500,   // How many miliseconds to wait at the end of scrolling

        "scroll-direction": "horizontal" // override the defaults assumed based on text-wrap
    }

Images

Probably should write something here.

Lists

The list system is quite adequate

Basic presentation options

    "element_name": {
        "select-color":    [255, 128, 128],  // color to draw the selected item as
        "select-fill":     [128, 128, 128],  // color to fill behind the selected item
        "alt-fill":        [210, 210, 210],  // color to fill alternating rows
        "no-select-color": [128, 128, 128],  // color to draw text on unselectable rows.
        "no-select-fill":  [210, 210, 210],  // color to fill on unselectable rows.
        "inactive-select-color": [128,  64,  64],  // color to draw the selected item if the element is inactive
    }

Pointer

    "element_name": {
        "pointer": "pointer.png",                 // pointer image
        "pointer-align": ["midright", "midleft"], // poisition of (1) list/text to attach the position (2) of the pointer to
        "pointer-size": [128, 64],                // scale it to this size, otherwise use the size of the image provided
        "pointer-attach": "text",                 // attach to the rendered "text" rect, or the "list" item rect.
        "pointer-offset": [0, 0],                 // offset it by x,y pixels after the position is calculated.
        "pointer-flip-x": false,                  // flip the image horizontally
        "pointer-flip-y": false,                  // flip it vertically
        "pointer-mirror": false,                  // mirror the pointer and display on the other side of the list item
        "pointer-mirror-x": false,                // flip the mirrored pointer horizontally
        "pointer-mirror-y": false,                // flip the mirrored pointer vertically
    }

#config

The #config section in a scene is used for changing the behaviour of a scene, currently it is only used for setting button mappings in the port_info and port_info_popup scenes, but will be expanded as needed.

By default the port_info scene is defined as:

    "port_info": {
        "#config": {
            "buttons": {
                "UP": "prev_port",
                "DOWN": "next_port"
            }
        }
    }

However if a port_info_popup is present it will default to this:

    "port_info": {
        "#config": {
            "buttons": {
                "LEFT": "prev_port",
                "RIGHT": "next_port",
                "UP": "port_info_popup"
            }
        }
    },

    "port_info_popup": {
        "#config": {
            "buttons": {
                "DOWN": "pop_scene"
            }
        }
    }

The code will automatically set the buttons for the button bar. The actions prev_port and next_port do not add to the button bar to stop if from being crowded.

Currently the only actions are:

More will be added as required.

Special Words

Within the templating system we have special words for things like button bindings and checkboxes. They are used for showing buttons and actions, and checkboxes.

It is possible to either override these special words with text, or replace them with an image.

Special Word Description
_A Button A
_B Button B
_X Button X
_Y Button Y
_UP Button UP
_DOWN Button DOWN
_LEFT Button LEFT
_RIGHT Button RIGHT
_START Button START
_SELECT Button SELECT
_L Button L
_R Button R
_CHECKED Checked item in a list
_UNCHECKED Unchecked item in a list

To replace the words with text you can simply add the following to your theme, like used in the basic_theme :

    "#override": {
        "_A":         "A:",
        "_B":         "B:",
        "_X":         "X:",
        "_Y":         "Y:",
        "_UP":        "UP:",
        "_DOWN":      "DOWN:",
        "_LEFT":      "LEFT:",
        "_RIGHT":     "RIGHT:",
        "_START":     "START:",
        "_SELECT":    "SELECT:",
        "_L":         "L",
        "_R":         "R",
        "_CHECKED":   "[x]",
        "_UNCHECKED": "[  ]",
    }

To replace them with images you will need to load either images and use name to override the image name, or use an image atlas like in the default_theme .

    "#resources": {
        "buttons.png": {
            "atlas": {
                "_A":         [  0,   0, 180, 180],
                "_B":         [180,   0, 180, 180],
                "_X":         [360,   0, 180, 180],
                "_Y":         [540,   0, 180, 180],
                "_UP":        [900, 360, 180,  90],
                "_DOWN":      [900, 450, 180,  90],
                "_LEFT":      [900, 180,  90, 180],
                "_RIGHT":     [990, 180,  90, 180],
                "_START":     [  0, 180, 360, 180],
                "_SELECT":    [360, 180, 360, 180],
                "_L":         [  0, 360, 270, 180],
                "_R":         [270, 360, 270, 180],
                "_CHECKED":   [900,   0, 180, 180],
                "_UNCHECKED": [720,   0, 180, 180]
            }
        }
    }

Text Template System

PortMaster has a simple text templating engine, it supports tags and if/then/else statements.

You can use the tags in text areas and they will automatically update as their value changes.

An example of using the system.time_24hr tag:

    {
        // Element options
        "text": "{system.time_24hr}"
    }

If no data is found for a tag the tag name is returned.

    {
        // This will display "{system.unknown_tag}"
        "text": "{system.unknown_tag}"
    }

Obviously multiple tags can be used at once:

    {
        // This might display "ArkOS (07232023)" on ArkOS for example"
        "text": "OS: {system.cfw_name} ({system.cfw_version})"
    }

As mentioned above you can use if/then/else statements in the text:

    {
        // If port_info.runtime is not false it will display "\nRuntime: {}"
        "text": "{if:port_info.runtime}\nRuntime: {port_info.runtime} ({port_info.runtime_status}){else}No Runtime Required{endif}"
    }

You can even compare two tags text:

    {
        // if ports_list.total_ports doesnt equal ports_list.filter_ports
        "text": "{if:!ports_list.total_ports::ports_list.filtered}{ports_list.filter_ports} / {ports_list.total_ports}{else}{ports_list.total_ports}{endif}"
    }

The format is:

{if:[!]<KEYNAME>[:<EQUALS TEXT>[:OTHER KEY]]}<TRUTH TEXT>[{else}<ELSE TEXT>]{endif}

Hopefully this helps, if not... oh well i will write it better later on.

Tag Result
{if:!port_info.runtime} port_info.runtime != ""
{if:port_info.runtime} port_info.runtime == ""
{if:port_info.runtime:Mono 6.12.0.122} port_info.runtime == "Mono 6.12.0.122"
{if:!port_info.runtime:Mono 6.12.0.122} port_info.runtime != "Mono 6.12.0.122"
{if:ports_list.total_ports::ports_list.filter_ports} ports_list.total_ports == ports_list.filter_ports
{if:!ports_list.total_ports::ports_list.filter_ports} ports_list.total_ports != ports_list.filter_ports

System tags

Port info tags

Ports List tags

Theme info tags

Control flow of PortMaster

Main:
  -> Install Menu
  -> Uninstall Menu -> Port List [Installed filter]
  -> Options
  -> Quit

Install Menu:
  -> All Ports    -> Port List [No filter]
  -> Ready To Run -> Port List [RTR filter]

  -> Lists 1      -> Custom List
  -> Lists 2      -> Custom List
  -> Lists 3      -> Custom List
  -> Lists 4      -> Custom List

  -> Back

Port List:
  -> List of Ports
    -> Inspect
    -> Back

Options:
  -> TBD.
  -> Back

Inspect:
  -> Install/Re-Install or Uninstall
  -> Back

Install/Uninstall:
  -> Message Screen
  -> Back