From b8614162cdbb3817a7a58ceadf8b6b0d05fae952 Mon Sep 17 00:00:00 2001 From: Frederico Linhares Date: Wed, 24 May 2023 14:50:01 -0300 Subject: feat Recreate the Menu --- lib/menu.rb | 220 +++++++++++++++++++++++++++++++++++++++++++++++++ test/Rakefile | 9 +- test/src/main.rb | 107 ++++++------------------ test/src/mode/demo.rb | 113 +++++++++++++++++++++++++ test/src/mode/title.rb | 49 +++++++++++ test/textures/menu.qoi | Bin 0 -> 849 bytes 6 files changed, 416 insertions(+), 82 deletions(-) create mode 100644 lib/menu.rb create mode 100644 test/src/mode/demo.rb create mode 100644 test/src/mode/title.rb create mode 100644 test/textures/menu.qoi diff --git a/lib/menu.rb b/lib/menu.rb new file mode 100644 index 0000000..9f4c55c --- /dev/null +++ b/lib/menu.rb @@ -0,0 +1,220 @@ +# Copyright 2022-2023 Frederico de Oliveira Linhares +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module CandyGear + class Menu + class Stack + def initialize(menu) + @stack = []; + @stack << menu; + end + + def push(menu) = @stack << menu; + + def pop() + @stack.pop() if @stack.size > 1; + end + + def draw() = @stack.each {_1.draw();} + def size() = @stack.size; + end + + class BorderlessView + attr_reader(:texture, :font, :text_color, :bg_color, :sprites); + + def initialize(texture, font, sprite_width, sprite_height) + @texture = texture; + @font = font; + @sprite_width = sprite_width; + @sprite_height = sprite_height; + + @sprites = { + arrow_select: Sprite.new( + @texture, Vector4D.new(1.0/3.0, 1.0/3.0, 2.0/3.0, 2.0/3.0)) + } + end + + def draw(x, y, width, height) + # Nothing. + end + + def border_width() = 0; + def border_height() = 0; + end + + class BorderedView + attr_reader(:texture, :font, :sprites, :border_width, :border_height); + + def initialize(texture, font, sprite_width, sprite_height) + @texture = texture; + @font = font; + @border_width = sprite_width; + @border_height = sprite_height; + + @sprites = { + arrow_select: Sprite.new( + @texture, Vector4D.new(1.0/3.0, 1.0/3.0, 2.0/3.0, 2.0/3.0)), + box_top_left: Sprite.new( + @texture, Vector4D.new(0.0, 0.0, 1.0/3.0, 1.0/3.0)), + box_top: Sprite.new( + @texture, Vector4D.new(1.1/3.0, 0.0, 1.9/3.0, 1.0/3.0)), + box_top_right: Sprite.new( + @texture, Vector4D.new(2.0/3.0, 0.0, 1.0, 1.0/3.0)), + box_left: Sprite.new( + @texture, Vector4D.new(0.0, 1.1/3.0, 1.0/3.0, 1.9/3.0)), + box_right: Sprite.new( + @texture, Vector4D.new(2.0/3.0, 1.1/3.0, 1.0, 1.9/3.0)), + box_bottom_left: Sprite.new( + @texture, Vector4D.new(0.0, 2.0/3.0, 1.0/3.0, 1.0)), + box_bottom: Sprite.new( + @texture, Vector4D.new(1.1/3.0, 2.0/3.0, 1.9/3.0, 1.0)), + box_bottom_right: Sprite.new( + @texture, Vector4D.new(2.0/3.0, 2.0/3.0, 1.0, 1.0)) + } + end + + def draw(view, x, y, width, height) + num_horizontal_sprites = width / @border_width + 1; + num_horizontal_sprites += 1 if width % @border_width > 0; + num_vertical_sprites = height / @border_height; + num_vertical_sprites += 1 if height % @border_height > 0; + + # Draw the corners. + @sprites[:box_top_left].draw( + view, Vector4D.new(x, y, border_width, border_height)); + @sprites[:box_top_right].draw( + view, Vector4D.new( + @border_width * (num_horizontal_sprites + 1) + x, y, + border_width, border_height)); + @sprites[:box_bottom_left].draw( + view, Vector4D.new( + x, @border_height * (num_vertical_sprites + 1) + y, + border_width, border_height)); + @sprites[:box_bottom_right].draw( + view, Vector4D.new( + @border_width * (num_horizontal_sprites + 1) + x, + @border_height * (num_vertical_sprites + 1) + y, + border_width, border_height)); + + # Draw the edges. + num_horizontal_sprites.times do |i| + # Top + @sprites[:box_top].draw( + view, Vector4D.new(@border_width * (i + 1) + x, y, + border_width, border_height)); + # Bottom + @sprites[:box_bottom].draw( + view, Vector4D.new(@border_width * (i + 1) + x, + @border_height * (num_vertical_sprites + 1) + y, + border_width, border_height)); + end + num_vertical_sprites.times do |i| + # Left + @sprites[:box_left].draw( + view, Vector4D.new( + x, @border_height * (i + 1) + y, border_width, border_height)); + # Right + @sprites[:box_right].draw( + view, Vector4D.new( + @border_width * (num_horizontal_sprites + 1) + x, + @border_height * (i + 1) + y, + border_width, border_height)); + end + end + end + + class Option + attr_reader(:action, :text, :width, :height); + + def initialize(text, action, width, height); + @text = text; + @action = action; + @width = width; + @height = height; + end + end + + attr_reader(:width, :height); + + def initialize(view, menu_view, pos_x, pos_y, options) + @view = view; + @menu_view = menu_view; + + @pos_x = pos_x; + @pos_y = pos_y; + + @options = options.map do |opt| + texture = Texture.from_text(menu_view.font, opt[:text]) + Option.new( + Sprite.new(texture, CandyGear::Vector4D.new(0, 0, 1.0, 1.0)), + opt[:action], + texture.width, + texture.height); + end + @current_option = 0; + @option_max_width = 0; + @option_max_height = 0; + + @options.each do |i| + if @option_max_width < i.width then + @option_max_width = i.width; + end + if @option_max_height < i.height then + @option_max_height = i.height; + end + end + + @width = @option_max_width; + @height = @option_max_height * @options.size; + end + + def next_opt() + @current_option += 1; + @current_option = 0 if @current_option >= @options.size(); + end + + def pred_opt() + if @current_option <= 0 then + @current_option = @options.size - 1; + else + @current_option -= 1; + end + end + + def activate() + @options[@current_option].action.call(); + end + + def draw() + @menu_view.draw(@view, @pos_x, @pos_y, @width, @height); + + @options.each_with_index do |opt, i| + opt.text.draw( + @view, Vector4D.new( + @pos_x + @menu_view.border_width + + @menu_view.border_width, + @pos_y + @menu_view.border_height + + @option_max_height * i, + opt.width, opt.height)); + end + + @menu_view.sprites[:arrow_select].draw( + @view, Vector4D.new( + @pos_x + @menu_view.border_width, + @pos_y + @menu_view.border_height + + @option_max_height * @current_option, + @menu_view.border_width, @menu_view.border_height)); + end + end +end diff --git a/test/Rakefile b/test/Rakefile index c449fad..c219bca 100644 --- a/test/Rakefile +++ b/test/Rakefile @@ -13,11 +13,18 @@ # limitations under the License. OBJ = 'test' +SOURCE_DIR = ENV['DESTDIR'] || '' + +RB_LIBS_PATH = "#{SOURCE_DIR}/usr/local/share/candy_gear/lib" +RB_LIBS = [ + 'menu' +] RB_FILES = FileList['src/**/*.rb'] task :build do - rb_files = RB_FILES.inject('') {_1 + "#{_2} "} + rb_files = RB_FILES.inject('') {_1 + "#{_2} "} + + RB_LIBS.inject('') {_1 + "#{RB_LIBS_PATH}/#{_2}.rb "} `mrbc -g -o #{OBJ}.mrb #{rb_files}` end diff --git a/test/src/main.rb b/test/src/main.rb index bc28ffc..98dcb9f 100644 --- a/test/src/main.rb +++ b/test/src/main.rb @@ -12,104 +12,49 @@ # See the License for the specific language governing permissions and # limitations under the License. -CAMERA_ROTATION_SPEED = Math::PI/45; -BOX_ROTATION_SPEED = Math::PI/180; -TRANSLATION_SPEED = 0.5; +def change_mode(new_mode) + $next_stage = new_mode; + $quit_stage = true; +end def config() CandyGear.game_name = "Candy Gear Test"; CandyGear::Graphic.display_width = 1280; CandyGear::Graphic.display_height = 720; + CandyGear::Graphic.fps = 60; end def init() - texture = CandyGear::Texture.from_image("textures/color_texture.qoi"); - mesh = CandyGear::Mesh.new("meshes/cube.cgmesh"); + menu_texture = CandyGear::Texture.from_image("textures/menu.qoi"); # FIXME: Text rendering crashes with this font: - # font = CandyGear::Font.new("/usr/share/fonts/TTF/sazanami-mincho.ttf", 16); + # font = CandyGear::Font.new( + # "/usr/share/fonts/TTF/sazanami-mincho.ttf", 16); # FIXME: This font, under this path, may not be present in all Linuxes: - font = CandyGear::Font.new("/usr/share/fonts/TTF/HanaMinA.ttf", 30); - japanese_text = CandyGear::Texture.from_text(font, "こんにちは世界!"); - english_text = CandyGear::Texture.from_text( - font, "The quick brown fox jumps"); - - $color = CandyGear::Vector3D.new(0.8, 0.2, 0.2); - $model = CandyGear::Model.new(mesh, texture); - $sprite = CandyGear::Sprite.new( - texture, CandyGear::Vector4D.new(0, 0, 1.0, 1.0)); - $rectangle = CandyGear::Vector4D.new(103.0, 1.0, 100.0, 100.0); - $sprite_position = CandyGear::Vector4D.new(1.0, 1.0, 100.0, 100.0); - $japanese_text_sprite = CandyGear::Sprite.new( - japanese_text, CandyGear::Vector4D.new(0, 0, 1.0, 1.0)); - $japanese_text_position = CandyGear::Vector4D.new( - 204.0, 1.0, japanese_text.width, japanese_text.height); - $english_text_sprite = CandyGear::Sprite.new( - english_text, CandyGear::Vector4D.new(0, 0, 1.0, 1.0)); - $english_text_position = CandyGear::Vector4D.new( - 204.0, japanese_text.height + 2.0, - english_text.width, english_text.height); - - $instances = [ - CandyGear::Vector3D.new(5.0, 0.0, 0.0), - CandyGear::Vector3D.new(-5.0, 0.0, 0.0), - CandyGear::Vector3D.new(0.0, 5.0, 0.0), - CandyGear::Vector3D.new(0.0, -5.0, 0.0), - CandyGear::Vector3D.new(0.0, 0.0, 5.0), - CandyGear::Vector3D.new(0.0, 0.0, -5.0) - ]; - $instances_rotation = CandyGear::Rotation3D.new(0.0, 0.0, 0.0); - - $camera_position = CandyGear::Vector3D.new(0.0, 0.0, 0.0); - $camera_rotation = CandyGear::Rotation3D.new(0.0, 0.0, 0.0); - - color = CandyGear::Vector3D.new(0.12, 0.12, 0.18); - $view1 = CandyGear::View2D.new( - CandyGear::Vector4D.new(0, 0, 1280, 240), 640, 120); - $view2 = CandyGear::View3D.new( - CandyGear::Vector4D.new(0, 240, 1280, 480), 1280, 480); - CandyGear.views = [$view1, $view2]; + font = CandyGear::Font.new("/usr/share/fonts/TTF/HanaMinA.ttf", 18); + $global_data = { + font: font, + menu_view: CandyGear::Menu::BorderedView.new(menu_texture, font, 16, 16) + } - $view2.camera_position = $camera_position; - $view2.camera_rotation = $camera_rotation; + change_mode(:title); end -def key_down(key) - case key - when CandyGear::Key::I - $camera_rotation.rotate(-CAMERA_ROTATION_SPEED, 0.0); - when CandyGear::Key::K - $camera_rotation.rotate(CAMERA_ROTATION_SPEED, 0.0); - when CandyGear::Key::J - $camera_rotation.rotate(0.0, CAMERA_ROTATION_SPEED); - when CandyGear::Key::L - $camera_rotation.rotate(0.0, -CAMERA_ROTATION_SPEED); - when CandyGear::Key::E - $camera_position.translate( - CandyGear::Vector3D.new(0.0, 0.0, -TRANSLATION_SPEED), $camera_rotation); - when CandyGear::Key::D - $camera_position.translate( - CandyGear::Vector3D.new(0.0, 0.0, TRANSLATION_SPEED), $camera_rotation); - when CandyGear::Key::S - $camera_position.translate( - CandyGear::Vector3D.new(-TRANSLATION_SPEED, 0.0, 0.0), $camera_rotation); - when CandyGear::Key::F - $camera_position.translate( - CandyGear::Vector3D.new(TRANSLATION_SPEED, 0.0, 0.0), $camera_rotation); - end -end +def key_down(key) = $mode.key_down(key); -def key_up(key) -end +def key_up(key) = $mode.key_up(key); def quit() = CandyGear.quit(); def tick() - $sprite.draw($view1, $sprite_position); - $japanese_text_sprite.draw($view1, $japanese_text_position); - $english_text_sprite.draw($view1, $english_text_position); - $instances_rotation.rotate(0.0, BOX_ROTATION_SPEED); - $rectangle.draw_rectangle($view1, $color); - $instances.each do |i| - $model.draw(i, $instances_rotation); + if $quit_stage then + case $next_stage + when :title + $mode = Mode::Title.new(); + else + $mode = Mode::Demo.new(); + end + $quit_stage = false; end + + $mode.tick(); end diff --git a/test/src/mode/demo.rb b/test/src/mode/demo.rb new file mode 100644 index 0000000..b3be78d --- /dev/null +++ b/test/src/mode/demo.rb @@ -0,0 +1,113 @@ +# Copyright 2022-2023 Frederico de Oliveira Linhares +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mode + class Demo + CAMERA_ROTATION_SPEED = Math::PI/45; + BOX_ROTATION_SPEED = Math::PI/360; + TRANSLATION_SPEED = 0.5; + + def initialize() + texture = CandyGear::Texture.from_image("textures/color_texture.qoi"); + mesh = CandyGear::Mesh.new("meshes/cube.cgmesh"); + font = CandyGear::Font.new("/usr/share/fonts/TTF/HanaMinA.ttf", 30); + japanese_text = CandyGear::Texture.from_text( + font, "こんにちは世界!"); + english_text = CandyGear::Texture.from_text( + font, "The quick brown fox jumps"); + + @color = CandyGear::Vector3D.new(0.8, 0.2, 0.2); + @model = CandyGear::Model.new(mesh, texture); + @sprite = CandyGear::Sprite.new( + texture, CandyGear::Vector4D.new(0, 0, 1.0, 1.0)); + @rectangle = CandyGear::Vector4D.new(103.0, 1.0, 100.0, 100.0); + @sprite_position = CandyGear::Vector4D.new(1.0, 1.0, 100.0, 100.0); + @japanese_text_sprite = CandyGear::Sprite.new( + japanese_text, CandyGear::Vector4D.new(0, 0, 1.0, 1.0)); + @japanese_text_position = CandyGear::Vector4D.new( + 204.0, 1.0, japanese_text.width, japanese_text.height); + @english_text_sprite = CandyGear::Sprite.new( + english_text, CandyGear::Vector4D.new(0, 0, 1.0, 1.0)); + @english_text_position = CandyGear::Vector4D.new( + 204.0, japanese_text.height + 2.0, + english_text.width, english_text.height); + + @instances = [ + CandyGear::Vector3D.new(5.0, 0.0, 0.0), + CandyGear::Vector3D.new(-5.0, 0.0, 0.0), + CandyGear::Vector3D.new(0.0, 5.0, 0.0), + CandyGear::Vector3D.new(0.0, -5.0, 0.0), + CandyGear::Vector3D.new(0.0, 0.0, 5.0), + CandyGear::Vector3D.new(0.0, 0.0, -5.0) + ]; + @instances_rotation = CandyGear::Rotation3D.new(0.0, 0.0, 0.0); + + @camera_position = CandyGear::Vector3D.new(0.0, 0.0, 0.0); + @camera_rotation = CandyGear::Rotation3D.new(0.0, 0.0, 0.0); + + color = CandyGear::Vector3D.new(0.12, 0.12, 0.18); + @view1 = CandyGear::View2D.new( + CandyGear::Vector4D.new(0, 0, 1280, 240), 640, 120); + @view2 = CandyGear::View3D.new( + CandyGear::Vector4D.new(0, 240, 1280, 480), 1280, 480); + CandyGear.views = [@view1, @view2]; + + @view2.camera_position = @camera_position; + @view2.camera_rotation = @camera_rotation; + end + + def key_down(key) + case key + when CandyGear::Key::I + @camera_rotation.rotate(-CAMERA_ROTATION_SPEED, 0.0); + when CandyGear::Key::K + @camera_rotation.rotate(CAMERA_ROTATION_SPEED, 0.0); + when CandyGear::Key::J + @camera_rotation.rotate(0.0, CAMERA_ROTATION_SPEED); + when CandyGear::Key::L + @camera_rotation.rotate(0.0, -CAMERA_ROTATION_SPEED); + when CandyGear::Key::E + @camera_position.translate( + CandyGear::Vector3D.new( + 0.0, 0.0, -TRANSLATION_SPEED), @camera_rotation); + when CandyGear::Key::D + @camera_position.translate( + CandyGear::Vector3D.new( + 0.0, 0.0, TRANSLATION_SPEED), @camera_rotation); + when CandyGear::Key::S + @camera_position.translate( + CandyGear::Vector3D.new( + -TRANSLATION_SPEED, 0.0, 0.0), @camera_rotation); + when CandyGear::Key::F + @camera_position.translate( + CandyGear::Vector3D.new( + TRANSLATION_SPEED, 0.0, 0.0), @camera_rotation); + end + end + + def key_up(key) + end + + def tick() + @sprite.draw(@view1, @sprite_position); + @japanese_text_sprite.draw(@view1, @japanese_text_position); + @english_text_sprite.draw(@view1, @english_text_position); + @instances_rotation.rotate(0.0, BOX_ROTATION_SPEED); + @rectangle.draw_rectangle(@view1, @color); + @instances.each do |i| + @model.draw(i, @instances_rotation); + end + end + end +end diff --git a/test/src/mode/title.rb b/test/src/mode/title.rb new file mode 100644 index 0000000..6347b70 --- /dev/null +++ b/test/src/mode/title.rb @@ -0,0 +1,49 @@ +# Copyright 2022-2023 Frederico de Oliveira Linhares +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mode + class Title + def initialize() + @view = CandyGear::View2D.new( + CandyGear::Vector4D.new(0, 0, 1280, 720), 640, 360); + CandyGear.views = [@view]; + + @menu = CandyGear::Menu.new( + @view, + $global_data[:menu_view], 10, 10, + [ + {text: "Demo", action: -> {change_mode(:demo);}}, + {text: "Quit", action: -> {CandyGear.quit();}} + ]); + end + + def key_down(key) + case key + when CandyGear::Key::I + @menu.pred_opt(); + when CandyGear::Key::K + @menu.next_opt(); + when CandyGear::Key::F + @menu.activate(); + end + end + + def key_up(key) + end + + def tick() + @menu.draw(); + end + end +end diff --git a/test/textures/menu.qoi b/test/textures/menu.qoi new file mode 100644 index 0000000..177a547 Binary files /dev/null and b/test/textures/menu.qoi differ -- cgit v1.2.3