Instantly share code, notes, and snippets.
Last active
December 25, 2025 18:39
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save larskanis/6a0fd4b75866c0e2b20ea76f31c013d5 to your computer and use it in GitHub Desktop.
A console-based startmenu for Rubyinstaller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require "io/console" | |
| class ButtonMatrix | |
| attr_accessor :selected | |
| def initialize(ncols: 3) | |
| @ncols = ncols | |
| @boxes = [] | |
| @con = IO.console | |
| @selected = 0 | |
| end | |
| Box = Data.define :text, :action | |
| def add_button(text, &action) | |
| @boxes << Box.new(text, action) | |
| end | |
| def cursor(direction) | |
| case direction | |
| when :left | |
| @selected -= 1 if @selected > 0 | |
| when :up | |
| @selected -= @ncols if @selected - @ncols >= 0 | |
| when :right | |
| @selected += 1 if @selected + 1 < @boxes.size | |
| when :down | |
| @selected += @ncols if @selected + @ncols < @boxes.size | |
| end | |
| end | |
| def select | |
| @boxes[@selected].action.call | |
| end | |
| BUTTON_BORDERS = { | |
| thin: "┌─┐" + | |
| "│ │" + | |
| "└─┘" , | |
| fat: "┏━┓" + | |
| "┃ ┃" + | |
| "┗━┛" , | |
| double: "╔═╗" + | |
| "║ ║" + | |
| "╚═╝" , | |
| } | |
| private def box_border(ncol, nrow) | |
| box_idx = ncol + nrow * @ncols | |
| selected = box_idx == @selected | |
| border = BUTTON_BORDERS[selected ? :double : :thin] | |
| box = @boxes[box_idx]&.text | |
| return box, border | |
| end | |
| # Paint the boxes | |
| def repaint(width: @con.winsize[1], height: @con.winsize[0] - 1) | |
| obw = (width.to_f / @ncols).floor | |
| spw = obw - 4 | |
| nrows = (@boxes.size.to_f / @ncols).ceil | |
| obh = (height.to_f / nrows).floor | |
| sph = obh - 2 | |
| line = +"" | |
| nrows.times do |nrow| | |
| @ncols.times do |ncol| | |
| box, border = box_border(ncol, nrow) | |
| if box | |
| line += " #{border[0]}" + "#{border[1]}" * spw + "#{border[2]} " | |
| end | |
| end | |
| line += "\n" | |
| sph.times do |spy| | |
| @ncols.times do |ncol| | |
| box, border = box_border(ncol, nrow) | |
| if box | |
| text_lines = box.lines.map(&:chomp) | |
| text_pos = spy - (sph - text_lines.size) / 2 | |
| text_line = text_lines[text_pos] if text_pos >= 0 | |
| text_line ||= "" | |
| spl = (spw - text_line.size) / 2 | |
| spr = spw - spl - text_line.size | |
| line += " #{border[3]}" + " " * spl + text_line + " " * spr + "#{border[5]} " | |
| end | |
| end | |
| line += "\n" | |
| end | |
| @ncols.times do |ncol| | |
| box, border = box_border(ncol, nrow) | |
| if box | |
| line += " #{border[6]}" + "#{border[7]}" * spw + "#{border[8]} " | |
| end | |
| end | |
| line += "\n" | |
| end | |
| print line | |
| end | |
| end | |
| class ConsoleApp | |
| attr_accessor :widget | |
| def initialize | |
| @ev_r, @ev_w = IO.pipe | |
| register_term_size_change | |
| register_stdin | |
| end | |
| private def register_term_size_change | |
| if RUBY_PLATFORM =~ /mingw|mswin/ | |
| IO.console.check_winsize_changed do | |
| @ev_w.write "\x01" | |
| end | |
| else | |
| Signal.trap('SIGWINCH') do | |
| @ev_w.write "\x01" | |
| end | |
| end | |
| end | |
| private def register_stdin | |
| Thread.new do | |
| str = +"" | |
| while char=$stdin.getch(intr: true) | |
| str << char | |
| next if !str.valid_encoding? || str == "\e" || str == "\e[" | |
| @ev_w.write [2, str.size, str].pack("CCa*") | |
| str = +"" | |
| end | |
| end | |
| end | |
| private def handle_key_input(str) | |
| case str | |
| when "\e[D" # cursor left | |
| widget.cursor(:left) | |
| when "\e[A" # cursor up | |
| widget.cursor(:up) | |
| when "\e[C" # cursor right | |
| widget.cursor(:right) | |
| when "\e[B" # cursor down | |
| widget.cursor(:down) | |
| when "\r" # enter | |
| widget.select | |
| end | |
| widget.repaint | |
| end | |
| private def main_loop | |
| str = +"" | |
| while char=@ev_r.getc | |
| case char | |
| when "\x01" | |
| widget.repaint | |
| when "\x02" | |
| strlen = @ev_r.getc.unpack1("C") | |
| str = @ev_r.read(strlen) | |
| handle_key_input(str) | |
| widget.repaint | |
| else | |
| raise "unexpected event: #{char.inspect}" | |
| end | |
| end | |
| end | |
| def run! | |
| widget.repaint | |
| main_loop | |
| end | |
| end | |
| app = ConsoleApp.new | |
| bm = ButtonMatrix.new ncols: 3 | |
| bm.add_button "text1\nabc" do | |
| p :button_1 | |
| exit | |
| end | |
| bm.add_button "text2" do | |
| p :button_2 | |
| end | |
| bm.add_button "text3\nabc\noollla\n text3\nabc\noollla" do | |
| p :button_3 | |
| end | |
| bm.add_button "text4\ndef" do | |
| p :button_4 | |
| end | |
| bm.add_button "text5\nabc" do | |
| p :button_5 | |
| end | |
| app.widget = bm | |
| app.run! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment