Files
rubbs/lib/bbs/windows/button_bar.rb
Zsolt Tasnadi db8a97b365 Relayout open windows on terminal resize
NAWS mid-session updates now propagate to the window stack: Application
re-runs each open window's relayout on resize. Window#relayout re-centers
(clamped to the screen edge) by shifting all descendants; MasterDetail
recomputes its full geometry and re-renders the detail at the new width.
ButtonBar placement is extracted into ButtonBar.place for reuse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:11:27 +02:00

107 lines
3.8 KiB
Ruby

# frozen_string_literal: true
require_relative '../widgets'
module BBS
module Windows
# Lays out a row of buttons at the bottom of a window.
#
# Button descriptor:
# { key:, label:, on_click:, primary:, cancel:, align:, keep_open: }
#
# `on_click` receives the BBS::Window as its only argument and may return
# :keep to keep the window open. Any other return value (or nil) causes
# the window to be closed after the handler.
#
# `primary: true` gets initial focus on the button.
# `cancel: true` is informational only — ESC closes the window through the
# standard Window#close_key path. If you also want a cancel-specific side
# effect on ESC, set `window.on_close = ...` manually.
# `align: :left | :right | :center` — default is :center for a single
# button, :right otherwise. Left-aligned buttons stack from the left edge;
# right-aligned buttons stack flush against the right edge in declaration
# order (so the last right-aligned button sits closest to the edge).
module ButtonBar
module_function
# Returns { widgets:, primary:, by_key: }.
def attach(window, app, descriptors)
return { widgets: [], primary: nil, by_key: {} } if descriptors.nil? || descriptors.empty?
widgets = descriptors.map do |d|
BBS::Widgets::Button.new(label: d[:label], on_click: build_handler(window, app, d))
end
place(window, widgets, descriptors)
widgets.each { |w| window.add(w) }
primary_idx = descriptors.index { |d| d[:primary] }
primary = primary_idx ? widgets[primary_idx] : nil
by_key = {}
descriptors.each_with_index { |d, i| by_key[d[:key]] = widgets[i] if d[:key] }
{ widgets: widgets, primary: primary, by_key: by_key }
end
# Position (or re-position) already-built bar widgets against the
# window's current bounds. Used by attach and by window relayouts.
def place(window, widgets, descriptors)
return if widgets.empty?
default_align = descriptors.length == 1 ? :center : :right
aligns = descriptors.map { |d| d[:align] || default_align }
row_y = window.bounds.y + window.bounds.height - 2
left_x0 = window.bounds.x + 2
right_x1 = window.bounds.x + window.bounds.width - 2
win_x0 = window.bounds.x
win_w = window.bounds.width
x = left_x0
widgets.each_with_index do |w, i|
next unless aligns[i] == :left
bw = button_width(w)
w.layout(x, row_y, bw, 1)
x += bw + 1
end
right_indices = aligns.each_index.select { |i| aligns[i] == :right }
total_right = right_indices.sum { |i| button_width(widgets[i]) + 1 } - 1
total_right = [total_right, 0].max
x = right_x1 - total_right + 1
right_indices.each do |i|
bw = button_width(widgets[i])
widgets[i].layout(x, row_y, bw, 1)
x += bw + 1
end
center_indices = aligns.each_index.select { |i| aligns[i] == :center }
total_center = center_indices.sum { |i| button_width(widgets[i]) + 1 } - 1
total_center = [total_center, 0].max
x = win_x0 + (win_w - total_center) / 2
center_indices.each do |i|
bw = button_width(widgets[i])
widgets[i].layout(x, row_y, bw, 1)
x += bw + 1
end
end
def button_width(button)
"[ #{button.display_label} ]".length
end
def build_handler(window, app, descriptor)
on_click = descriptor[:on_click]
keep_open = descriptor[:keep_open]
lambda do |_btn|
result = on_click&.call(window)
app.close_window(window) unless keep_open || result == :keep
:handled
end
end
end
end
end