new framework

This commit is contained in:
2026-05-12 22:15:07 +02:00
parent be21826ecc
commit 953498a62d
24 changed files with 2410 additions and 180 deletions

88
lib/bbs/statusbar.rb Normal file
View File

@@ -0,0 +1,88 @@
# frozen_string_literal: true
require_relative 'widget'
module BBS
# A status bar at the bottom of the screen. Render a list of hotkey hints
# like "F1=Help F2=Save Esc=Cancel". Optionally a right-aligned status text.
class Statusbar < Widget
Hint = Struct.new(:key, :label, :on_activate, keyword_init: true)
attr_accessor :hints, :right_text
def initialize(hints: [], right_text: nil, **kw)
super(**kw)
@hints = hints
@right_text = right_text
end
def add_hint(key, label, &on_activate)
@hints << Hint.new(key: key, label: label, on_activate: on_activate)
end
def render(frame)
sgr = style(:statusbar)
key_sgr = style(:statusbar_key)
frame.move(bounds.x, bounds.y)
frame.write(' ' * bounds.width, sgr: sgr)
cx = bounds.x + 1
@hints.each do |hint|
text_key = hint.key.to_s
text_lbl = " #{hint.label} "
break if cx + text_key.length + text_lbl.length > bounds.x + bounds.width - 1
frame.move(cx, bounds.y)
frame.write(text_key, sgr: key_sgr)
frame.write(text_lbl, sgr: sgr)
cx += text_key.length + text_lbl.length + 1
end
if @right_text
txt = @right_text.to_s
rx = bounds.x + bounds.width - txt.length - 1
if rx >= cx
frame.move(rx, bounds.y)
frame.write(txt, sgr: sgr)
end
end
end
def handle_event(event)
if event.key?
hint = match_key(event.key)
return hint.on_activate&.call || :handled if hint
return nil
end
return nil unless event.mouse? && event.mouse.kind == :press &&
event.mouse.button == :left && event.y == bounds.y
cx = bounds.x + 1
@hints.each do |hint|
text_key = hint.key.to_s
text_lbl = " #{hint.label} "
width = text_key.length + text_lbl.length
if event.x >= cx && event.x < cx + width
return hint.on_activate&.call || :handled
end
cx += width + 1
end
nil
end
private
def match_key(key)
key_str = key.to_s
@hints.find do |hint|
label = hint.key.to_s
case label
when /\AF(\d+)\z/i then key == :"f#{::Regexp.last_match(1)}"
when /\AAlt-(.)\z/i then key == :"alt_#{::Regexp.last_match(1).downcase}"
when /\AEsc\z/i then key == :escape
when /\ATab\z/i then key == :tab
else key_str.casecmp?(label)
end
end
end
end
end