83 lines
2.2 KiB
Ruby
83 lines
2.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module BBS
|
|
# Base widget. All visual elements inherit from this.
|
|
#
|
|
# bounds — Rect (x, y, width, height) in screen coordinates
|
|
# parent — enclosing Container, or nil
|
|
# id — optional symbol for lookup via Container#find
|
|
#
|
|
# Subclasses override render(frame) and optionally handle_event(event).
|
|
class Widget
|
|
Rect = Struct.new(:x, :y, :width, :height) do
|
|
def contains?(px, py)
|
|
px >= x && px < x + width && py >= y && py < y + height
|
|
end
|
|
|
|
def to_s = "##{x},#{y} #{width}x#{height}"
|
|
end
|
|
|
|
attr_accessor :bounds, :parent, :id, :visible, :focused, :enabled
|
|
attr_reader :theme
|
|
|
|
def initialize(id: nil, bounds: nil, theme: nil)
|
|
@id = id
|
|
@bounds = bounds || Rect.new(1, 1, 0, 0)
|
|
@parent = nil
|
|
@visible = true
|
|
@focused = false
|
|
@enabled = true
|
|
@theme = theme
|
|
end
|
|
|
|
# Override in subclasses. By default a widget does not accept focus.
|
|
def focusable? = false
|
|
|
|
# Called when this widget gains focus. Subclasses may override.
|
|
def on_focus = nil
|
|
|
|
# Called when this widget loses focus.
|
|
def on_blur = nil
|
|
|
|
# Render this widget into the supplied FrameBuffer.
|
|
def render(frame); end
|
|
|
|
# Handle a keyboard or mouse event. Return :halt to end the application,
|
|
# :handled to stop propagation, or nil/false to continue.
|
|
def handle_event(_event) = nil
|
|
|
|
# Convenience: yield this widget. Subclasses override to walk a tree.
|
|
def each(&block)
|
|
yield self
|
|
nil
|
|
end
|
|
|
|
# Hit test: returns the deepest widget whose bounds contain (x, y).
|
|
def hit_test(x, y)
|
|
bounds.contains?(x, y) ? self : nil
|
|
end
|
|
|
|
# Apply a theme. Subclasses with children propagate via Container#theme=.
|
|
def theme=(value)
|
|
@theme = value
|
|
end
|
|
|
|
# Move/resize. Subclasses may override to relayout children.
|
|
def layout(x, y, width, height)
|
|
@bounds = Rect.new(x, y, width, height)
|
|
end
|
|
|
|
# Request a redraw — set by Application as a callback if needed.
|
|
def redraw!
|
|
@parent&.redraw!
|
|
end
|
|
|
|
protected
|
|
|
|
def style(key)
|
|
th = @theme || (parent && parent.theme)
|
|
th ? th.style(key) : nil
|
|
end
|
|
end
|
|
end
|