diff --git a/lib/cyberarm_engine.rb b/lib/cyberarm_engine.rb index 273d251..29704dc 100644 --- a/lib/cyberarm_engine.rb +++ b/lib/cyberarm_engine.rb @@ -37,7 +37,7 @@ require_relative "cyberarm_engine/ui/event" require_relative "cyberarm_engine/ui/style" require_relative "cyberarm_engine/ui/border_canvas" require_relative "cyberarm_engine/ui/element" -require_relative "cyberarm_engine/ui/elements/label" +require_relative "cyberarm_engine/ui/elements/text_block" require_relative "cyberarm_engine/ui/elements/button" require_relative "cyberarm_engine/ui/elements/toggle_button" require_relative "cyberarm_engine/ui/elements/list_box" diff --git a/lib/cyberarm_engine/ui/dsl.rb b/lib/cyberarm_engine/ui/dsl.rb index b47d6d4..114f10f 100644 --- a/lib/cyberarm_engine/ui/dsl.rb +++ b/lib/cyberarm_engine/ui/dsl.rb @@ -8,11 +8,29 @@ module CyberarmEngine container(CyberarmEngine::Element::Stack, options, &block) end + # TODO: Remove in version 0.16.0+ def label(text, options = {}, &block) options[:parent] = element_parent options[:theme] = current_theme - add_element(Element::Label.new(text, options, block)) + add_element(Element::TextBlock.new(text, options, block)) + end + + [ + "Banner", + "Title", + "Subtitle", + "Tagline", + "Caption", + "Para", + "Inscription" + ].each do |const| + define_method(:"#{const.downcase}") do |text, options, &block| + options[:parent] = element_parent + options[:theme] = current_theme + + add_element(Element.const_get(const).new(text, options, block)) + end end def button(text, options = {}, &block) diff --git a/lib/cyberarm_engine/ui/elements/button.rb b/lib/cyberarm_engine/ui/elements/button.rb index f9a711e..a039325 100644 --- a/lib/cyberarm_engine/ui/elements/button.rb +++ b/lib/cyberarm_engine/ui/elements/button.rb @@ -1,6 +1,6 @@ module CyberarmEngine class Element - class Button < Label + class Button < TextBlock def initialize(text_or_image, options = {}, block = nil) @image = nil @scale_x = 1 diff --git a/lib/cyberarm_engine/ui/elements/check_box.rb b/lib/cyberarm_engine/ui/elements/check_box.rb index f456314..c0b2677 100644 --- a/lib/cyberarm_engine/ui/elements/check_box.rb +++ b/lib/cyberarm_engine/ui/elements/check_box.rb @@ -6,7 +6,7 @@ module CyberarmEngine options[:toggled] = options[:checked] @toggle_button = ToggleButton.new(options) - @label = Label.new(text, options) + @label = TextBlock.new(text, options) @label.subscribe(:holding_left_mouse_button) do |sender, x, y| @toggle_button.left_mouse_button(sender, x, y) diff --git a/lib/cyberarm_engine/ui/elements/label.rb b/lib/cyberarm_engine/ui/elements/label.rb index b815cf2..6b60af2 100644 --- a/lib/cyberarm_engine/ui/elements/label.rb +++ b/lib/cyberarm_engine/ui/elements/label.rb @@ -1,6 +1,6 @@ module CyberarmEngine class Element - class Label < Element + class TextBlock < Element def initialize(text, options = {}, block = nil) super(options, block) @@ -127,5 +127,30 @@ module CyberarmEngine publish(:changed, self.value) end end + + class Banner < TextBlock + end + + class Title < TextBlock + end + + class Subtitle < TextBlock + end + + class Tagline < TextBlock + end + + class Caption < TextBlock + end + + class Para < TextBlock + end + + class Inscription < TextBlock + end + + # TODO: Remove in version 0.16.0+ + class Label < TextBlock + end end end diff --git a/lib/cyberarm_engine/ui/elements/text_block.rb b/lib/cyberarm_engine/ui/elements/text_block.rb new file mode 100644 index 0000000..3f5e022 --- /dev/null +++ b/lib/cyberarm_engine/ui/elements/text_block.rb @@ -0,0 +1,155 @@ +module CyberarmEngine + class Element + class TextBlock < Element + def initialize(text, options = {}, block = nil) + super(options, block) + + @text = Text.new( + text, font: @options[:font], z: @z, color: @options[:color], + size: @options[:text_size], shadow: @options[:text_shadow], + shadow_size: @options[:text_shadow_size], + shadow_color: @options[:text_shadow_color] + ) + + @raw_text = text + end + + def render + @text.draw + end + + def clicked_left_mouse_button(_sender, _x, _y) + @block&.call(self) if @enabled + + # return :handled + end + + def recalculate + @width = 0 + @height = 0 + + _width = dimensional_size(@style.width, :width) + _height = dimensional_size(@style.height, :height) + + handle_text_wrapping(_width) + + @width = _width || @text.width.round + @height = _height || @text.height.round + + @text.y = @style.border_thickness_top + @style.padding_top + @y + @text.z = @z + 3 + + if (text_alignment = @options[:text_align]) + case text_alignment + when :left + @text.x = @style.border_thickness_left + @style.padding_left + @x + when :center + @text.x = if @text.width <= outer_width + @x + outer_width / 2 - @text.width / 2 + else # Act as left aligned + @style.border_thickness_left + @style.padding_left + @x + end + when :right + @text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right) + end + end + + update_background + end + + def handle_text_wrapping(max_width) + max_width ||= @parent&.width + max_width ||= @x - (window.width + noncontent_width) + wrap_behavior = style.text_wrap + copy = @raw_text.to_s.dup + + if max_width >= line_width(copy[0]) && line_width(copy) > max_width && wrap_behavior != :none + breaks = [] + line_start = 0 + line_end = copy.length + + while line_start != copy.length + if line_width(copy[line_start...line_end]) > max_width + line_end = ((line_end - line_start) / 2.0) + elsif line_end < copy.length && line_width(copy[line_start...line_end + 1]) < max_width + # To small, grow! + # TODO: find a more efficient way + line_end += 1 + + else # FOUND IT! + entering_line_end = line_end.floor + max_reach = line_end.floor - line_start < 63 ? line_end.floor - line_start : 63 + reach = 0 + + if wrap_behavior == :word_wrap + max_reach.times do |i| + reach = i + break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /) + end + + puts "Max width: #{max_width}/#{line_width(@raw_text)} Reach: {#{reach}/#{max_reach}} Line Start: #{line_start}/#{line_end.floor} (#{copy.length}|#{@raw_text.length}) [#{entering_line_end}] '#{copy}' {#{copy[line_start...line_end]}}" + line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation + end + + breaks << line_end.floor + line_start = line_end.floor + line_end = copy.length + + break if entering_line_end == copy.length || reach == max_reach + end + end + + breaks.each_with_index do |pos, index| + copy.insert(pos + index, "\n") if pos + index >= 0 && pos + index < copy.length + end + end + + @text.text = copy + end + + def line_width(text) + (@x + @text.textobject.markup_width(text) + noncontent_width) + end + + def value + @raw_text + end + + def value=(value) + @raw_text = value.to_s.chomp + + old_width = width + old_height = height + recalculate + + root.gui_state.request_recalculate if old_width != width || old_height != height + + publish(:changed, self.value) + end + end + + class Banner < TextBlock + end + + class Title < TextBlock + end + + class Subtitle < TextBlock + end + + class Tagline < TextBlock + end + + class Caption < TextBlock + end + + class Para < TextBlock + end + + class Inscription < TextBlock + end + + class ToolTip < TextBlock + end + end +end diff --git a/lib/cyberarm_engine/ui/gui_state.rb b/lib/cyberarm_engine/ui/gui_state.rb index a8892c2..e32d33a 100644 --- a/lib/cyberarm_engine/ui/gui_state.rb +++ b/lib/cyberarm_engine/ui/gui_state.rb @@ -26,7 +26,7 @@ module CyberarmEngine @dragging_element = nil @pending_recalculate_request = false - @tip = CyberarmEngine::Text.new("", size: 22, z: Float::INFINITY) + @tip = Element::ToolTip.new("", parent: @root_container, z: Float::INFINITY) @menu = nil @min_drag_distance = 0 @mouse_pos = Vector.new @@ -51,9 +51,8 @@ module CyberarmEngine @menu.draw end - if @tip.text.length.positive? + if @tip.value.length.positive? Gosu.flush - Gosu.draw_rect(@tip.x - 2, @tip.y - 2, @tip.width + 4, @tip.height + 4, 0xff020202, Float::INFINITY) @tip.draw end end @@ -87,11 +86,17 @@ module CyberarmEngine if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay - @tip.text = @mouse_over.tip if @mouse_over + @tip.value = @mouse_over.tip if @mouse_over @tip.x = window.mouse_x - @tip.width / 2 - @tip.y = window.mouse_y - @tip.height - 4 + @tip.x = 0 if @tip.x < 0 + @tip.x = window.width - @tip.width if @tip.x + @tip.width > window.width + @tip.y = window.mouse_y - @tip.height + @tip.y = 0 if @tip.y < 0 + @tip.y = window.height - @tip.height if @tip.y + @tip.height > window.height + @tip.update + @tip.recalculate else - @tip.text = "" + @tip.value = "" end else @mouse_moved_at = Gosu.milliseconds @@ -107,7 +112,7 @@ module CyberarmEngine end def tool_tip_delay - 500 # ms + 250 # ms end def button_down(id) diff --git a/lib/cyberarm_engine/ui/theme.rb b/lib/cyberarm_engine/ui/theme.rb index a2bf2c9..9531066 100644 --- a/lib/cyberarm_engine/ui/theme.rb +++ b/lib/cyberarm_engine/ui/theme.rb @@ -106,7 +106,7 @@ module CyberarmEngine retro: false }, - Label: { # < Element + TextBlock: { # < Element text_size: 28, text_wrap: :none, # :word_wrap, :break_word, :none text_shadow: false, @@ -116,6 +116,45 @@ module CyberarmEngine padding: 2 }, + Banner: { # < TextBlock + text_size: 48 + }, + + Title: { # < TextBlock + text_size: 34 + }, + + Subtitle: { # < TextBlock + text_size: 26 + }, + + Tagline: { # < TextBlock + text_size: 24 + }, + + Caption: { # < TextBlock + text_size: 22 + }, + + Para: { # < TextBlock + text_size: 18 + }, + + Inscription: { # < TextBlock + text_size: 16 + }, + + ToolTip: { # < TextBlock + color: Gosu::Color::WHITE, + padding_top: 4, + padding_bottom: 4, + padding_left: 8, + padding_right: 8, + border_thickness: 1, + border_color: 0xffaaaaaa, + background: 0xff404040 + }, + ToggleButton: { # < Button checkmark: "√" },