diff --git a/artwork/sad-phone.svg b/artwork/sad-phone.svg deleted file mode 100644 index 7d9af34e..00000000 --- a/artwork/sad-phone.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/artwork/sad.svg b/artwork/sad.svg new file mode 100644 index 00000000..f5ad28b2 --- /dev/null +++ b/artwork/sad.svg @@ -0,0 +1,94 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/boot/error/main.rb b/boot/error/main.rb index 59182267..82dd3ad5 100644 --- a/boot/error/main.rb +++ b/boot/error/main.rb @@ -1,12 +1,4 @@ begin -# Get exclusive control of the framebuffer -# By design we will not restore the console at exit. -# We are assuming the target does not necessarily have a console attached to -# the framebuffer, so this program has to be enough by itself. -VTConsole.map_console(0) - -# Prepare LVGL -LVGL::Hacks.init() data = JSON.parse(File.read(ARGV.first)) @@ -15,19 +7,26 @@ $color = data["color"] $delay = data["delay"] $message = data["message"] $status = data["status"] +$title = data["title"] $color = $color.rjust(6, "0").rjust(8, "F").to_i(16) class UI def initialize() - screen - sad_phone - code - message - time_left + add_screen + + # First add the title bar, other elements will sit under. + add_title_bar + # Then, action pane, so we can get its height + add_actions_pane + # Finally, messages pane, which takes the remainder. + add_messages_pane + + # Re-compute the layout + relayout() end - def screen() + def add_screen() @screen = LVGL::LVContainer.new() # Create a new style @@ -38,69 +37,195 @@ class UI style.body_grad_color = $color end - def sad_phone() - file = nil - file = "/sad-phone.svg" if File.exist?("/sad-phone.svg") - file = "sad-phone.svg" if File.exist?("sad-phone.svg") - return unless file + def add_title_bar() + @title_bar = LVGL::LVContainer.new(@screen) + @title_bar.set_width(@screen.get_width()) + @title_bar.set_height(16*unit) + @title_bar.set_pos(0, 0) - if @screen.get_height > @screen.get_width - LVGL::Hacks::LVNanoSVG.resize_next_width(@screen.get_width) - else - LVGL::Hacks::LVNanoSVG.resize_next_height(@screen.get_height) + @title_bar.get_style(LVGL::CONT_STYLE::MAIN).dup.tap do |style| + @title_bar.set_style(LVGL::CONT_STYLE::MAIN, style) + style.body_main_color = 0x00000000 + style.body_grad_color = 0x00000000 + style.body_border_width = 0 + style.body_radius = 0 + style.body_opa = (255 * 0.30).to_i end + add_sad_phone + add_title + end + + def add_actions_pane() + @actions_pane = LVGL::LVContainer.new(@screen) + @actions_pane.set_width(get_pane_width()) + + # When horizontal, we know it's placed to the right, full height. + if horizontal? + @actions_pane.set_height(@screen.get_height() - @title_bar.get_height()) + @actions_pane.set_pos(get_pane_width(), @title_bar.get_height()) + end + + @actions_pane.get_style(LVGL::CONT_STYLE::MAIN).dup.tap do |style| + @actions_pane.set_style(LVGL::CONT_STYLE::MAIN, style) + style.body_main_color = 0x00000000 + style.body_grad_color = 0x00000000 + style.body_border_width = 0 + style.body_radius = 0 + style.body_opa = (255 * 0.20).to_i + end + + add_time_left + end + + def add_messages_pane() + @messages_pane = LVGL::LVContainer.new(@screen) + @messages_pane.set_width(get_pane_width()) + @messages_pane.set_pos(0, @title_bar.get_height()) + + # When horizontal, full height + if horizontal? + @messages_pane.set_height(@screen.get_height() - @title_bar.get_height()) + end + + @messages_pane.get_style(LVGL::CONT_STYLE::MAIN).dup.tap do |style| + @messages_pane.set_style(LVGL::CONT_STYLE::MAIN, style) + style.body_main_color = 0x00000000 + style.body_grad_color = 0x00000000 + style.body_border_width = 0 + style.body_radius = 0 + style.body_opa = (255 * 0).to_i + end + + add_message_title + add_message + end + + # Title elements + + def add_sad_phone() + file = nil + file = "/sad.svg" if File.exist?("/sad.svg") + file = "sad.svg" if File.exist?("sad.svg") + return unless file + + LVGL::Hacks::LVNanoSVG.resize_next_height(@title_bar.get_height - 2 * padding) @sad_phone = LVGL::LVImage.new(@screen) @sad_phone.set_src(file) + @sad_phone.set_pos(2 * padding, padding) + end - # Center the image - @sad_phone.set_pos( - @screen.get_width / 2 - @sad_phone.get_width / 2, - @screen.get_height / 2 - @sad_phone.get_height / 2, + def add_title() + @title = new_text($code, parent: @title_bar) + @title.set_align(LVGL::LABEL_ALIGN::LEFT) + @title.set_x(@sad_phone.get_x()*2 + @sad_phone.get_width()) + @title.set_y(@title_bar.get_height / 2 - @title.get_height / 2) + @title.set_width(@title_bar.get_width() - @title.get_x()) + end + + # Messages pane elements + + def add_message_title() + @message_title = new_text($title, parent: @messages_pane) + @message_title.set_align(LVGL::LABEL_ALIGN::LEFT) + @message_title.set_pos( + padding, + padding, ) end - def code() - @code = ShadedText.new(@screen) - @code.set_long_mode(LVGL::LABEL_LONG::BREAK) - @code.set_align(LVGL::LABEL_ALIGN::CENTER) - @code.set_width((@screen.get_width * 0.8).to_i) - @code.set_text($code) - @code.set_pos( - @screen.get_width / 2 - @code.get_width / 2, - (@screen.get_height * 0.05).to_i - ) - end - - def message() - @message = ShadedText.new(@screen) - @message.set_long_mode(LVGL::LABEL_LONG::BREAK) - @message.set_align(LVGL::LABEL_ALIGN::CENTER) - @message.set_width((@screen.get_width * 0.95).to_i) - @message.set_text($message) + def add_message() + @message = new_text($message, parent: @messages_pane) + @message.set_align(LVGL::LABEL_ALIGN::LEFT) @message.set_pos( - @screen.get_width / 2 - @message.get_width / 2, - (@screen.get_height * 0.65).to_i + padding, + @message_title.get_height() + @message_title.get_y() + padding ) end - def time_left() - @time_left = ShadedText.new(@screen) - @time_left.set_long_mode(LVGL::LABEL_LONG::BREAK) - @time_left.set_align(LVGL::LABEL_ALIGN::CENTER) - @time_left.set_width((@screen.get_width * 0.95).to_i) + # Actions pane elements + def add_time_left() + @time_left = new_text("", parent: @actions_pane) set_time_left($delay) - - @time_left.set_pos( - @screen.get_width / 2 - @time_left.get_width / 2, - (@screen.get_height - @time_left.get_height * 1.5) - ) + @time_left.set_x(@actions_pane.get_width / 2 - @time_left.get_width / 2) + @time_left.set_y(padding) end + # Layout helpers + + def relayout() + unless horizontal? + @messages_pane.set_height( + @screen.get_height() - @title_bar.get_height() - @actions_pane.get_height() + ) + + # The actions pane has to be as high as required in vertical mode. + last_element = @actions_pane.get_children.reduce do |a, b| + if b + a_end = a.get_y() + a.get_height() + b_end = b.get_y() + b.get_height() + if a_end > b_end + a + else + b + end + else + a + end + end + + @actions_pane.set_height( + last_element.get_y() + last_element.get_height() + padding + ) + + # Always push it as far down as possible! + @actions_pane.set_pos(0, @screen.get_height() - @actions_pane.get_height()) + end + end + + # Misc. helpers + + # Creates a label with some useful defaults. + def new_text(text, parent: nil) + parent ||= @screen + + el = ShadedText.new(parent) + el.set_long_mode(LVGL::LABEL_LONG::BREAK) + el.set_align(LVGL::LABEL_ALIGN::CENTER) + el.set_width((parent.get_width - 2*padding).to_i) + el.set_text(text) + el + end + + # Updates the UI with the time left. def set_time_left(value) @time_left.set_text("#{value} seconds left before crashing.") end + + def get_pane_width() + if horizontal? + @screen.get_width() / 2 + else + @screen.get_width() + end + end + + def unit() + (if horizontal? + @screen.get_height() + else + @screen.get_width() + end) / 128 + end + + def padding() + 2 * unit + end + + def horizontal?() + @screen.get_height < @screen.get_width + end end # Create the UI diff --git a/modules/initrd-fail.nix b/modules/initrd-fail.nix index 088ac9e1..e917e094 100644 --- a/modules/initrd-fail.nix +++ b/modules/initrd-fail.nix @@ -28,8 +28,8 @@ in config = { mobile.boot.stage-1.contents = [ { - object = (builtins.path { path = ../artwork/sad-phone.svg; }); - symlink = "/sad-phone.svg"; + object = (builtins.path { path = ../artwork/sad.svg; }); + symlink = "/sad.svg"; } ]; };