Tickit::Style(3pm)

✨ Tickit::Style ✨

okay bestie, so this is a page from the perl manual β€” Tickit::Style(3pm) if you wanna be formal about it πŸ’… β€” and it's literally the glow-up your widgets have been waiting for.

πŸ’– NAME πŸ’–

Tickit::Style β€” declare customisable style information on widgets. yes, that's the whole vibe. slay.


🌸 SYNOPSIS 🌸

package My::Widget::Class
use base qw( Tickit::Widget );
use Tickit::Style;

style_definition base =>
   fg => "red";

style_definition ':active' =>
   b => 1;

...

sub render_to_rb
{
   my $self = shift;
   my ( $rb, $rect ) = @_;

   $rb->text_at( 0, 0, "Here is my text", $self->get_style_pen );
}

use My::Widget::Class;

my $w = My::Widget::Class->new(
   class => "another-class",
);

...

like... look at her go πŸ¦‹


πŸ‘‘ DESCRIPTION πŸ‘‘

So here's the tea β˜•: this module lets a Tickit::Widget class declare a whole little collection of named keys that hold values, and then hands the widget some super convenient accessors so it can figure out what those values actually are at any given moment in time. The values that are currently in effect get determined by the widget class code, plus any stylesheet files the application has loaded. no cap.

The widget itself can also store a set of tags β€” named entities that are either present or absent. Which tags are currently active on the widget is one of the things that helps decide which style definitions get used. πŸ’…

And finally (last but never least ✨), the widget stores a list of style class names. Those classes also help decide which style definitions from a loaded stylesheet file get applied.

πŸŽ€ Stylesheet Files πŸŽ€

A stylesheet file is basically just a big list of definitions of styles. Every definition gives you:

  • a Tickit::Widget class name,
  • optionally a style class name prefixed by a period (.),
  • optionally a set of tags prefixed with colons (:),
  • and then a body definition in a brace-delimited ({}) block.

Comments can appear anywhere whitespace is allowed β€” they start with a hash symbol (#) and just keep going until the end of the line. iconic.

WidgetClass {
  # basic style goes here
}

WidgetClass.styleclass {
  # style to apply for this class goes here
}

WidgetClass:tag {
  # style to apply when this tag is active goes here
}

Each style definition contains a set of semicolon-delimited (;) assignments of values to keys. Every key gets a colon (:) after it, and the values may be integers, quoted strings (...), or the special identifiers true or false. periodt.

WidgetClass.styleclass {
  key1: "value 1";
  key2: 123;
  key3: true;
}

Now, traditionally keys in stylesheet files would contain hyphens (-), but in Perl code it's way more convenient to use underscores (_) instead. So the parser converts hyphens in key names into underscores for you. 🫢

Stylesheets don't just do visual styling either β€” they can associate behavioural actions with keypresses. You give a keypress key name in angle brackets (<NAME>) and an action name, which is a bareword identifier:

WidgetClass {
  <Enter>: activate;
}

And there's a special widget type name of * that gives you style blocks applying (at lower priority) to any type of widget. Typically you'd pair these with classes or tags to set application-wide styles 🌈:

*:error {
  bg: "red";
  fg: "hi-white";
}

πŸ¦‹ How Style is Determined πŸ¦‹

The full set of style definitions applied to one named class of one widget type, for all its style tags, is called a "tagset". Each tagset is made of a partially-ordered list of entities called "keysets", which map style keys to values for one particular set of active style tags. The widget may also have a special tagset holding the "direct-applied" style definition that got handed to the constructor.

The style at any given moment comes from taking into account the style classes and tags that are in effect. For each key, the value is decided by a first-match-wins search along:

  1. the "direct applied" tagset (if it's there),
  2. then the tagset for each of the style classes, in order,
  3. and finally the base tagset for the widget type without class.

Within each tagset, only the keysets that don't depend on an inactive style tag get considered. Which means: a keyset depending on no tags is always considered, and any keyset that only depends on active tags gets considered too β€” even if there are other active tags that the keyset doesn't consider. Tags are always additive in this regard, okay? πŸ’œ

Now, while the order of the tagsets is exactly defined by the order of the style classes applied to the widget, the order of keysets within each tagset is not fully specified. Tagsets are stored partially ordered, sorted by the number of style tags each keyset depends on. That makes sure more specific keysets get found first, and therefore override the less specific ones. But β€” and this is the important bit β€” the ordering of keysets with equal numbers of (distinct) tags is not defined. 😭

For instance, if both tag1 and tag2 are active, this stylesheet here does not precisely determine the foreground colour:

WidgetClass      { fg: "red"; }
WidgetClass:tag1 { fg: "blue"; }
WidgetClass:tag2 { fg: "green"; }

It is not specified which tagged definition takes precedence β€” so whether it ends up blue or green is literally up in the air πŸ™ˆ β€” but it is specified that both tagged definitions take precedence over the untagged one. So bestie, the colour will not be red. that part's locked in.


πŸ’€ SUBCLASSING πŸ’€

If a Widget class gets subclassed and the subclass does not declare use Tickit::Style again, then the subclass is transparent from the style point of view. Any style applied to the base class applies equally to the subclass, and the subclass's name doesn't take part in style decisions at all. invisible. ghosted. πŸ«₯

But if the subclass does use Tickit::Style again, then the new subclass becomes a distinct widget type for style purposes. It can optionally copy the style info from its base class, but after that the stored information is distinct β€” and changes in the base class (like loading style files) will not affect it. main character energy, honestly.

To copy the style information from the base, apply the -copy keyword:

use Tickit::Style -copy;

Or, to start with a brand new blank state, use the -blank keyword:

use Tickit::Style -blank;
⚠️ Currently, -blank is the default behaviour β€” but this may change in a future version, and if you don't specify a keyword you'll get a deprecation warning. so like, maybe be explicit? just a thought πŸŽ€

πŸ”₯ FUNCTIONS πŸ”₯

style_definition

style_definition( $tags, %definition );

On top of any loaded stylesheets, the widget class itself can hand over style information via the style_definition function. It provides a definition equivalent to a stylesheet definition with no style class, optionally with a single set of tags. To supply no tags at all, use the special string base.

style_definition base =>
   key1 => "value",
   key2 => 123;

And to provide definitions with tags, use the colon-prefixed notation:

style_definition ':active' =>
   key3 => "value";

style_reshape_keys

style_reshape_keys( @keys );

Declares that the given list of keys are somehow responsible for determining the shape of the widget. If their values change, the reshape method gets called. πŸ‘‘

style_reshape_textwidth_keys

style_reshape_textwidth_keys( @keys );

Declares that the given list of keys contain text, and that the textwidth() of that text is used to determine the shape of the widget. If their values change such that the textwidth() differs, the reshape method gets called.

style_redraw_keys

style_redraw_keys( @keys );

Declares that the given list of keys are somehow responsible for determining the look of the widget β€” but in a way that does not determine the size. If their values change, the redraw method gets called. ✨

Between them, these three methods may help avoid Tickit::Widget classes needing to override the on_style_changed_values method. so convenient fr fr.


🧚 ADDITIONAL FUNCTIONS/METHODS 🧚

Just so you know β€” these functions are not exported, but you can call them directly. πŸ’…

load_style

Tickit::Style->load_style( $string );

Loads definitions from a stylesheet given in a string.

Definitions get merged with existing definitions in memory, with new values overwriting existing values. πŸ“

load_style_file

Tickit::Style->load_style_file( $path );

Loads definitions from a stylesheet file given by the path.

Definitions get merged the same way as load_style.

load_style_from_DATA

Tickit::Style->load_style_from_DATA;

A convenient little shortcut for loading style definitions from the caller's DATA filehandle. πŸŽ€

on_style_load

Tickit::Style::on_style_load( \&code );

Adds a CODE reference to be invoked after either load_style or load_style_file are called. This may be useful for flushing any caches or invalidating any state that depends on style information. cleanse that cache bestie 🧼


πŸ’œ AUTHOR πŸ’œ

Paul Evans <leonerd@leonerd.org.uk>


and that's the whole page, babes 🫢 this beauty is perl v5.40.0, dated 2024-09-16, and it lives its life as Tickit::Style(3pm). go forth and style your widgets, you gorgeous thing πŸ’–βœ¨

πŸ“¦ where this page comes from