Class: Tuile::ComponentBackground

Inherits:
Object
  • Object
show all
Defined in:
lib/tuile/component_background.rb,
sig/tuile.rbs

Overview

One component's background: what it states, and the Color that resolves to right now. A component reaches its own through the protected Tuile::Component#bg; an app tints through Tuile::Component#bg_color=.

# a widget: its own well, brighter while focused
def initialize
super
bg.default_color = ComponentBackground::INPUT_WELL
end

panel.bg_color = Theme.ref(:panel_bg)   # an app: tint a whole subtree

The chain, first answer wins: the owner's Tuile::Component#error_bg_color, then #color (the app's), then #default_color (the widget's), then the parent's #effective, then nil — the terminal default. INHERIT at a level skips the owner's remaining levels and goes straight to the parent. Every level takes a Color, a Theme::Ref or a Hash keyed by STATES, and resolves against the live theme and the owner's Tuile::Component#active? at paint time, so nothing here caches a color.

Implementation details

The error level is pulled from the owner rather than stored here: it follows the validation state, and a pushed copy would need every edge of that state to remember to re-set it. A stale error well fails silently.

Constant Summary collapse

STATES =

The states a background may be keyed by. Closed and framework-defined: a key is added when Tuile grows the state, never to let an app invent one.

Returns:

  • (Array<Symbol>)
%i[normal active].freeze
INHERIT =

Assign to #color to say "I contribute no background of my own" — resolution skips the owner's #default_color and takes whatever surrounds it. CSS's background: inherit, and the reason a widget with a well can be made to sit flush in a tinted panel:

field.bg_color = ComponentBackground::INHERIT   # no well; take the pane's tint

Distinct from nil, which falls through to #default_color first. There is deliberately no counterpart forcing the terminal default despite a tinted ancestor (D_bg_inherit).

Returns:

  • (Symbol)
:inherit
INPUT_WELL =

The well every input field paints: Theme#input_bg_color at rest, Theme#active_bg_color while on the focus chain. Live Theme::Refs, so a Screen#theme= restyles it with no hook.

Returns:

{ normal: Theme.ref(:input_bg_color), active: Theme.ref(:active_bg_color) }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(owner) ⇒ ComponentBackground

@param owner — whose background this is.

Parameters:



55
56
57
58
59
# File 'lib/tuile/component_background.rb', line 55

def initialize(owner)
  @owner = owner
  @color = nil
  @default_color = nil
end

Instance Attribute Details

#color ⇒ Color, ...

@return — the app's background — the value as set, so a Theme::Ref comes back unresolved and a state map comes back a Hash; nil when unset.

Returns:



64
65
66
# File 'lib/tuile/component_background.rb', line 64

def color
  @color
end

#default_color ⇒ Color, ...

@return — the widget's own surface — nil by default, meaning "whatever is behind me shows through".

Returns:



83
84
85
# File 'lib/tuile/component_background.rb', line 83

def default_color
  @default_color
end

Instance Method Details

#ambient ⇒ Color?

What surrounds the owner — the app's #color, else whatever the parent paints. Skips #default_color and the error well, the owner's own surface, which is what makes it the right answer for a dead tail outside Tuile::Component#extent and a container's gaps.

Returns:



124
125
126
127
128
129
# File 'lib/tuile/component_background.rb', line 124

def ambient
  own = resolve(@color)
  return parent_effective if own.nil? || own == INHERIT

  own
end

#coerce(value) ⇒ Color, ...

Validates and normalizes a level's value, so a bad token or a misspelled state raises at the assignment rather than deep in a repaint. A chrome token needs no theme to check, which keeps construction screen-free.

@param value

Parameters:

  • value (Object)

Returns:



162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/tuile/component_background.rb', line 162

def coerce(value)
  case value
  when nil, Color, INHERIT then value
  when Theme::Ref
    value.tap { _1.resolve(@owner.screen.theme) unless Theme.chrome_token?(_1.name) }
  when Hash
    unknown = value.keys - STATES
    raise ArgumentError, "unknown background state(s) #{unknown.join(", ")}; known: #{STATES.join(", ")}" \
      unless unknown.empty?

    value.to_h { |state, color| [state, coerce(color)] }.freeze
  else Color.coerce(value)
  end
end

#effective ⇒ Color?

@return — the background actually painted, for the state the owner is in right now — the whole chain, resolved. Screen#canvas_for loads it onto the canvas; an app never needs it.

Returns:



112
113
114
115
116
117
# File 'lib/tuile/component_background.rb', line 112

def effective
  own = resolve(@owner.__send__(:error_bg_color)) || resolve(@color) || resolve(@default_color)
  return parent_effective if own.nil? || own == INHERIT

  own
end

#invalidate ⇒ void

This method returns an undefined value.



137
138
139
# File 'lib/tuile/component_background.rb', line 137

def invalidate
  @owner.walk_tree { |c| @owner.screen.invalidate(c) } if @owner.attached?
end

#parent_effective ⇒ Color?

Returns:



134
# File 'lib/tuile/component_background.rb', line 134

def parent_effective = @owner.parent&.__send__(:bg)&.effective

#resolve(value) ⇒ Color, ...

Collapses one level to the Tuile::Color it means right now. An absent state key yields nil, so resolution falls through to the next level — which is what lets bg_color = { active: … } keep the widget's own normal well.

@param value

Parameters:

Returns:



146
147
148
149
150
151
152
153
# File 'lib/tuile/component_background.rb', line 146

def resolve(value)
  case value
  when nil then nil
  when Hash then resolve(value[@owner.active? ? :active : :normal])
  when Theme::Ref then value.resolve(@owner.screen.theme)
  else value
  end
end