Compare commits

48 Commits

Author SHA1 Message Date
5d1c195917 Added support for rendering multiple lights, standardized shaders to use snake case for variables and camel case for functions, stubbed PBR material shader include. 2023-07-29 14:09:23 -05:00
9a6e1df032 Remove need to do a full gui recalc 3 or more times for the layout to work (current implementation slows things down a bit, but seems more reliable then brute forcing 3x+) 2023-06-18 19:42:38 -05:00
b1b8fc8556 Cache scroll width/height 2023-06-18 14:18:15 -05:00
a60b09a110 Further scrolling improvements (should be smoother/more consistent with varied frame time) 2023-06-18 13:50:32 -05:00
81a632942e Round scroll position to prevent rendering issues caused by floats 2023-06-18 12:38:12 -05:00
5ef8023aca Improved scrolling 2023-06-18 12:26:08 -05:00
2e690d7d33 Misc bug fixes and improvements 2023-06-16 14:40:16 -05:00
a9f9e20235 Remove named arguments from stats struct due to mruby lacking support 2023-04-28 22:29:53 -05:00
25c36d3788 Add CyberarmEngine::Stats::StatsPlotter for rendering frame timings graph along with labeled sub-timings 2023-04-20 21:54:44 -05:00
c26ddeef4d Refactored CyberarmEngine::Stats to track data for last N frames 2023-04-20 16:08:59 -05:00
5e3e06b74e Improve Vector arithmetic performance by 2x 2023-04-16 20:26:29 -05:00
72037efc73 Fixed Slider element not working properly under mruby (attempts to slide would jump between min/max values) 2023-03-27 17:13:51 -05:00
1462f89e24 Initial support for using cyberarm_engine as a mruby mrbgem: Remove/disable usages of defined?, update old gosu mouse and keyboard constants, and replace one usage of window.button_down? with proper Gosu.button_down? 2023-03-24 18:21:41 -05:00
98948c891a Request repaint when hiding menu 2023-03-17 11:05:17 -05:00
da16ab1ec9 Bump version 2023-02-01 15:48:29 -06:00
eb5d170733 Added #find_element_by_tag to Common module 2023-01-31 14:33:17 -06:00
14e9d4946f Add GameState#needs_repaint? and Container#remove 2023-01-31 10:17:51 -06:00
e3b8a9b102 Fixed overdrawing on BorderCanvas for left side, fixed BackgroundImage fill mode not filling correctly, fixed Progress in marquee mode not request repaint each frame. 2023-01-11 15:05:58 -06:00
458731a534 Added shaders from i-mic-fps, preload shaders if cyberarm_engine/opengl has been required. 2023-01-08 17:30:37 -06:00
d1d87db070 Made GuiState#update safe to call when it is not the active state 2023-01-08 03:36:33 -06:00
6e8948bd81 Repaint when Slider value changes 2023-01-06 16:14:18 -06:00
01a9187a57 Request a repaint when popping and shifting states 2023-01-05 08:36:38 -06:00
186ad220cc Added more triggers for repainting 2023-01-04 20:17:49 -06:00
f82c0953b2 Implemented support for dynamic repainting of gui states with GuiState#needs_repaint? 2023-01-03 22:16:07 -06:00
a2d44ea2dc Fix Element#scroll_height not accounting for padding_top and border_thickness_top 2023-01-02 16:45:43 -06:00
c46664778a Fixed Image element causing clipping issues to sibling elements due to not properly flooring @width/@height 2022-11-16 20:31:00 -06:00
c597a67ca6 Allow 1 pixel wide/tall Gui elements by only apply percentage based sizing on Floats, fixed max/min sizing not working if the element uses fill mode for either dimension 2022-10-30 11:57:20 -05:00
55382a7c14 Bump version 2022-10-23 18:40:46 -05:00
883de3db9f Added support for TextBlock's to have text_v_align to compliment text_h_align, EditLine and EditBox will now preserve their focus appear, fixed crash in ToggleButton due to using :Label instead of :TextBlock, misc. tweaks. 2022-10-23 18:38:51 -05:00
41c0b27937 Fixed layout bug for Flow when the element x + width is greater than the parent's width it would not correctly wrap 2022-10-20 09:36:20 -05:00
2fd5d398cf Fix crash when EditLine receives a paste from clipboard 2022-10-07 20:32:18 -05:00
2e66509f87 Removed use of the clipboard gem since Gosu now supports this natively! Added :static option for Text to render text using Gosu::Image.from_text/markup which results in nicer looking text, improvements to EditLine to display the caret in the correct place (doesn't position properly if :text_static is true) 2022-10-04 10:16:32 -05:00
521b3937dd Fixed Slider element's Handle not styling or positioning properly 2022-07-26 12:20:13 -05:00
ab9f9e8e7a Refactored dimentional_size to split out space_available_width/height, initial implementation of verticial and horizontal container alignment 2022-06-12 16:03:23 -05:00
705138f7ad Fixed elements in a Flow container not positioned correctly after the first wrap 2022-06-12 11:36:22 -05:00
94a65f447c Fixed long standing issue with ListBox menu not taking it's parents width only the parent has a specified width and fixed clicking on the menu's host element to hide the menu causes it to hide and reappear instantly. 2022-06-12 11:25:29 -05:00
97296b080c Bump version 2022-06-05 09:20:17 -05:00
ca73a2d8e8 Added all the callbacks to Window and GameState, removed all but one usage of global variables (). 2022-06-05 09:19:30 -05:00
0a62e5180a Add down arrow for ListBox 2022-05-03 20:01:34 -05:00
31e909eb30 Probably fix layout issues caused by floating point errors where an element is wrapped when it has space (e.g. 2 elements with a width 0.5 not fitting properly) 2022-05-02 19:08:47 -05:00
300e7c4e59 Implemented tiled mode for BackgroundImage and other improvements 2022-05-02 19:06:40 -05:00
be98fe47ad Fixed ListBox element menu getting cut off if it's near the bottom of the screen by stacking it ontop of the host element 2022-05-02 19:05:57 -05:00
a75afaf47a Added support for UI to have background_images, fixed TextBlock text overdrawing 2022-04-25 20:12:10 -05:00
d81df5f4e2 Moved min_width/height, max_width/height and fill control from Container into Element#dimensional_size so any element can use them 2022-04-25 16:48:17 -05:00
f2ea0d9942 Maybe final container fill mode fix? 2022-04-04 13:26:40 -05:00
08d068f156 Fixed weirdness for Container dynamic fill mode 2022-04-04 10:08:39 -05:00
a4d02038c3 Fixes for Container's calculating fill size and for Flow's max_width 2022-04-03 13:06:10 -05:00
37bdd6ef23 Added support for min/max width/height and for fill which dynamically sets elements width/height to the max available width for Flow parents and height for Stack parents 2022-04-03 11:07:31 -05:00
42 changed files with 1372 additions and 346 deletions

View File

@@ -0,0 +1,30 @@
# version 330 core
layout(location = 0) out vec3 fragPosition;
layout (location = 1) out vec4 fragColor;
layout (location = 2) out vec3 fragNormal;
layout (location = 3) out vec3 fragUV;
in vec3 out_position, out_color, out_normal, out_uv, out_frag_pos, out_camera_pos;
out vec4 outputFragColor;
flat in int out_has_texture;
uniform sampler2D diffuse_texture;
void main() {
vec3 result;
if (out_has_texture == 0) {
result = out_color;
} else {
result = texture(diffuse_texture, out_uv.xy).xyz + 0.25;
}
fragPosition = out_position;
fragColor = vec4(result, 1.0);
fragNormal = out_normal;
fragUV = out_uv;
float gamma = 2.2;
outputFragColor.rgb = pow(fragColor.rgb, vec3(1.0 / gamma));
}

View File

@@ -0,0 +1,69 @@
#version 330 core
out vec4 frag_color;
@include "light_struct"
const int DIRECTIONAL = 0;
const int POINT = 1;
const int SPOT = 2;
flat in Light out_lights[7];
in vec2 out_tex_coords;
flat in int out_light_count;
uniform sampler2D diffuse, position, texcoord, normal, depth;
vec4 directionalLight(Light light) {
vec3 norm = normalize(texture(normal, out_tex_coords).rgb);
vec3 diffuse_color = texture(diffuse, out_tex_coords).rgb;
vec3 frag_pos = texture(position, out_tex_coords).rgb;
vec3 lightDir = normalize(light.position - frag_pos);
float diff = max(dot(norm, lightDir), 0);
vec3 _ambient = light.ambient;
vec3 _diffuse = light.diffuse * diff;
vec3 _specular = light.specular;
return vec4(_diffuse + _ambient + _specular, 1.0);
}
vec4 pointLight(Light light) {
return vec4(0.25, 0.25, 0.25, 1);
}
vec4 spotLight(Light light) {
return vec4(0.5, 0.5, 0.5, 1);
}
vec4 calculateLighting(Light light) {
vec4 result;
// switch(light.type) {
// case DIRECTIONAL: {
// result = directionalLight(light);
// }
// case SPOT: {
// result = spotLight(light);
// }
// default: {
// result = pointLight(light);
// }
// }
if (light.type == DIRECTIONAL) {
result = directionalLight(light);
} else {
result = pointLight(light);
}
return result;
}
void main() {
frag_color = vec4(0.0);
for(int i = 0; i < out_light_count; i++)
{
frag_color += texture(diffuse, out_tex_coords) * calculateLighting(out_lights[i]);
}
}

View File

@@ -0,0 +1,11 @@
struct Light {
int type;
vec3 direction;
vec3 position;
vec3 diffuse;
vec3 ambient;
vec3 specular;
float intensity;
};

View File

@@ -0,0 +1,16 @@
struct Material {
vec3 color;
vec3 roughness;
vec3 metalic;
vec3 specular;
bool use_color_texture;
bool use_roughness_texture;
bool use_metalic_texture;
bool use_specular_texture;
sampler2D color_tex;
sampler2D roughness_tex;
sampler2D metalic_tex;
sampler2D specular_tex;
};

View File

@@ -0,0 +1,28 @@
# version 330 core
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec3 in_color;
layout(location = 2) in vec3 in_normal;
layout(location = 3) in vec3 in_uv;
uniform mat4 projection, view, model;
uniform int has_texture;
uniform vec3 camera_pos;
out vec3 out_position, out_color, out_normal, out_uv;
out vec3 out_frag_pos, out_view_pos, out_camera_pos;
flat out int out_has_texture;
void main() {
// projection * view * model * position
out_position = in_position;
out_color = in_color;
out_normal= normalize(transpose(inverse(mat3(model))) * in_normal);
out_uv = in_uv;
out_has_texture = has_texture;
out_camera_pos = camera_pos;
out_frag_pos = vec3(model * vec4(in_position, 1.0));
gl_Position = projection * view * model * vec4(in_position, 1.0);
}

View File

@@ -0,0 +1,24 @@
#version 330 core
@include "light_struct"
layout (location = 0) in vec3 in_position;
layout (location = 1) in vec2 in_tex_coords;
uniform sampler2D diffuse, position, texcoord, normal, depth;
uniform int light_count;
uniform Light lights[7];
out vec2 out_tex_coords;
flat out int out_light_count;
flat out Light out_lights[7];
void main() {
gl_Position = vec4(in_position.x, in_position.y, in_position.z, 1.0);
out_tex_coords = in_tex_coords;
out_light_count = light_count;
for(int i = 0; i < light_count; i++)
{
out_lights[i] = lights[i];
}
}

View File

@@ -27,7 +27,6 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = %w[lib assets] spec.require_paths = %w[lib assets]
spec.add_dependency "clipboard", "~> 1.3"
spec.add_dependency "excon", "~> 0.88" spec.add_dependency "excon", "~> 0.88"
spec.add_dependency "gosu", "~> 1.1" spec.add_dependency "gosu", "~> 1.1"
spec.add_dependency "gosu_more_drawables", "~> 0.3" spec.add_dependency "gosu_more_drawables", "~> 0.3"

View File

@@ -8,7 +8,6 @@ end
require "json" require "json"
require "excon" require "excon"
require "gosu_more_drawables" require "gosu_more_drawables"
require "clipboard"
require_relative "cyberarm_engine/version" require_relative "cyberarm_engine/version"
require_relative "cyberarm_engine/stats" require_relative "cyberarm_engine/stats"
@@ -24,6 +23,7 @@ require_relative "cyberarm_engine/transform"
require_relative "cyberarm_engine/ray" require_relative "cyberarm_engine/ray"
require_relative "cyberarm_engine/background" require_relative "cyberarm_engine/background"
require_relative "cyberarm_engine/background_nine_slice" require_relative "cyberarm_engine/background_nine_slice"
require_relative "cyberarm_engine/background_image"
require_relative "cyberarm_engine/animator" require_relative "cyberarm_engine/animator"
require_relative "cyberarm_engine/text" require_relative "cyberarm_engine/text"
@@ -66,6 +66,6 @@ require_relative "cyberarm_engine/model/material"
require_relative "cyberarm_engine/model/model_object" require_relative "cyberarm_engine/model/model_object"
require_relative "cyberarm_engine/model/parser" require_relative "cyberarm_engine/model/parser"
require_relative "cyberarm_engine/model/parsers/wavefront_parser" require_relative "cyberarm_engine/model/parsers/wavefront_parser"
require_relative "cyberarm_engine/model/parsers/collada_parser" if defined?(Nokogiri) require_relative "cyberarm_engine/model/parsers/collada_parser" if RUBY_ENGINE != "mruby" && defined?(Nokogiri)
require_relative "cyberarm_engine/builtin/intro_state" require_relative "cyberarm_engine/builtin/intro_state"

View File

@@ -0,0 +1,93 @@
module CyberarmEngine
class BackgroundImage
include CyberarmEngine::Common
attr_accessor :x, :y, :z, :mode
attr_reader :image, :width, :height, :color
def initialize(image_path: nil, x: 0, y: 0, z: 0, width: 0, height: 0, mode: :fill, color: Gosu::Color::WHITE)
@image_path = image_path
@image = get_image(image_path) if image_path
@x = x
@y = y
@z = z
@width = width
@height = height
@mode = mode
@color = color
@cached_tiling = nil
end
def image=(image_path)
@cached_tiling = nil if image_path != @image_path
@image_path = image_path
@image = image_path ? get_image(image_path) : image_path
end
def width=(n)
@cached_tiling = nil if @width != n
@width = n
end
def height=(n)
@cached_tiling = nil if @height != n
@height = n
end
def color=(c)
@cached_tiling = nil if @color != c
@color = c
end
def width_scale
(@width.to_f / @image.width).abs
end
def height_scale
(@height.to_f / @image.height).abs
end
def draw
return unless @image
Gosu.clip_to(@x, @y, @width, @height) do
send(:"draw_#{mode}")
end
end
def draw_stretch
@image.draw(@x, @y, @z, width_scale, height_scale, @color)
end
def draw_tiled
@cached_tiling ||= Gosu.record(@width, @height) do
height_scale.ceil.times do |y|
width_scale.ceil.times do |x|
@image.draw(x * @image.width, y * @image.height, @z, 1, 1, @color)
end
end
end
@cached_tiling.draw(@x, @y, @z)
end
def draw_fill
if (@image.width * width_scale) >= @width && (@image.height * width_scale) >= @height
draw_fill_width
else
draw_fill_height
end
end
def draw_fill_width
@image.draw(@x, @y, @z, width_scale, width_scale, @color)
end
def draw_fill_height
@image.draw(@x, @y, @z, height_scale, height_scale, @color)
end
end
end

View File

@@ -7,8 +7,8 @@ module CyberarmEngine
@title_size = 56 @title_size = 56
@caption_size = 24 @caption_size = 24
@title = CyberarmEngine::Text.new("", size: @title_size, shadow_color: 0xaa_222222) @title = CyberarmEngine::Text.new("", size: @title_size, shadow_color: 0xaa_222222, static: true)
@caption = CyberarmEngine::Text.new("", size: @caption_size, shadow_color: 0xaa_222222) @caption = CyberarmEngine::Text.new("", size: @caption_size, shadow_color: 0xaa_222222, static: true)
@spacer_width = 256 @spacer_width = 256
@spacer_height = 6 @spacer_height = 6
@@ -18,7 +18,7 @@ module CyberarmEngine
@gosu_logo = generate_proxy("Gosu", "Game Library", 0xff_111111) @gosu_logo = generate_proxy("Gosu", "Game Library", 0xff_111111)
@ruby_logo = generate_proxy("Ruby", "Programming Language", 0xff_880000) @ruby_logo = generate_proxy("Ruby", "Programming Language", 0xff_880000)
@opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if defined?(OpenGL) @opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if RUBY_ENGINE != "mruby" && defined?(OpenGL)
base_time = Gosu.milliseconds base_time = Gosu.milliseconds

View File

@@ -8,8 +8,11 @@ module CyberarmEngine
window.current_state window.current_state
end end
def previous_state def previous_state(state = nil)
window.previous_state raise "Only available for CyberarmEngine::GameState and subclasses" unless is_a?(CyberarmEngine::GameState) || state.is_a?(CyberarmEngine::GameState)
i = window.states.index(state || self)
window.states[i - 1] unless (i - 1).negative?
end end
def pop_state def pop_state
@@ -28,6 +31,18 @@ module CyberarmEngine
window.show_cursor = boolean window.show_cursor = boolean
end end
def find_element_by_tag(container, tag, list = [])
return unless container
container.children.each do |child|
list << child if child.style.tag == tag
find_element_by_tag(child, tag, list) if child.is_a?(CyberarmEngine::Element::Container)
end
list.first
end
def draw_rect(x, y, width, height, color, z = 0, mode = :default) def draw_rect(x, y, width, height, color, z = 0, mode = :default)
Gosu.draw_rect(x, y, width, height, color, z, mode) Gosu.draw_rect(x, y, width, height, color, z, mode)
end end
@@ -37,7 +52,7 @@ module CyberarmEngine
end end
def lighten(color, amount = 25) def lighten(color, amount = 25)
if defined?(color.alpha) if color.respond_to?(:alpha)
Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha) Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
else else
Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount) Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
@@ -45,7 +60,7 @@ module CyberarmEngine
end end
def darken(color, amount = 25) def darken(color, amount = 25)
if defined?(color.alpha) if color.respond_to?(:alpha)
Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha) Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
else else
Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount) Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
@@ -95,7 +110,7 @@ module CyberarmEngine
end end
def window def window
$window CyberarmEngine::Window.instance
end end
def control_down? def control_down?

View File

@@ -103,7 +103,7 @@ module CyberarmEngine
def button_down(id) def button_down(id)
case id case id
when Gosu::KbEnter, Gosu::KbReturn when Gosu::KB_ENTER, Gosu::KB_RETURN
return unless @text_input.text.length.positive? return unless @text_input.text.length.positive?
@history.text += "\n<c=999999>> #{@text_input.text}</c>" @history.text += "\n<c=999999>> #{@text_input.text}</c>"
@@ -113,12 +113,12 @@ module CyberarmEngine
handle_command handle_command
@text_input.text = "" @text_input.text = ""
when Gosu::KbUp when Gosu::KB_UP
@command_history_index -= 1 @command_history_index -= 1
@command_history_index = 0 if @command_history_index.negative? @command_history_index = 0 if @command_history_index.negative?
@text_input.text = @command_history[@command_history_index] @text_input.text = @command_history[@command_history_index]
when Gosu::KbDown when Gosu::KB_DOWN
@command_history_index += 1 @command_history_index += 1
if @command_history_index > @command_history.size - 1 if @command_history_index > @command_history.size - 1
@text_input.text = "" unless @command_history_index > @command_history.size @text_input.text = "" unless @command_history_index > @command_history.size
@@ -127,7 +127,7 @@ module CyberarmEngine
@text_input.text = @command_history[@command_history_index] @text_input.text = @command_history[@command_history_index]
end end
when Gosu::KbTab when Gosu::KB_TAB
split = @text_input.text.split(" ") split = @text_input.text.split(" ")
if !@text_input.text.end_with?(" ") && split.size == 1 if !@text_input.text.end_with?(" ") && split.size == 1
@@ -142,7 +142,7 @@ module CyberarmEngine
cmd.autocomplete(self) cmd.autocomplete(self)
end end
when Gosu::KbBacktick when Gosu::KB_BACKTICK
# Remove backtick character from input # Remove backtick character from input
@text_input.text = if @text_input.text.size > 1 @text_input.text = if @text_input.text.size > 1
@text_input.text[0..@text_input.text.size - 2] @text_input.text[0..@text_input.text.size - 2]
@@ -151,7 +151,7 @@ module CyberarmEngine
end end
# Copy # Copy
when Gosu::KbC when Gosu::KB_C
if control_down? && shift_down? if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end @memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
p @memory p @memory
@@ -160,7 +160,7 @@ module CyberarmEngine
end end
# Paste # Paste
when Gosu::KbV when Gosu::KB_V
if control_down? && shift_down? if control_down? && shift_down?
string = @text_input.text.chars.insert(caret_pos, @memory).join string = @text_input.text.chars.insert(caret_pos, @memory).join
_caret_pos = caret_pos _caret_pos = caret_pos
@@ -170,7 +170,7 @@ module CyberarmEngine
end end
# Cut # Cut
when Gosu::KbX when Gosu::KB_X
if control_down? && shift_down? if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end @memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
string = @text_input.text.chars string = @text_input.text.chars
@@ -182,7 +182,7 @@ module CyberarmEngine
end end
# Delete word to left of caret # Delete word to left of caret
when Gosu::KbW when Gosu::KB_W
if control_down? if control_down?
split = @text_input.text.split(" ") split = @text_input.text.split(" ")
split.delete(split.last) split.delete(split.last)
@@ -190,7 +190,7 @@ module CyberarmEngine
end end
# Clear history # Clear history
when Gosu::KbL when Gosu::KB_L
@history.text = "" if control_down? @history.text = "" if control_down?
end end
end end

View File

@@ -7,7 +7,7 @@ module CyberarmEngine
attr_reader :alpha attr_reader :alpha
def initialize(options = {}) def initialize(options = {})
$window.current_state.add_game_object(self) if options[:auto_manage] || options[:auto_manage].nil? window.current_state.add_game_object(self) if options[:auto_manage] || options[:auto_manage].nil?
@options = options @options = options
@image = options[:image] ? image(options[:image]) : nil @image = options[:image] ? image(options[:image]) : nil
@@ -42,7 +42,7 @@ module CyberarmEngine
@radius = if options[:radius] @radius = if options[:radius]
options[:radius] options[:radius]
else else
defined?(@image.width) ? ((@image.width + @image.height) / 4) * scale : 1 @image.respond_to?(:width) ? ((@image.width + @image.height) / 4) * scale : 1
end end
end end
end end
@@ -55,9 +55,9 @@ module CyberarmEngine
if $debug if $debug
show_debug_heading show_debug_heading
$window.draw_circle(@position.x, @position.y, radius, 9999, @debug_color) Gosu.draw_circle(@position.x, @position.y, radius, 9999, @debug_color)
if @debug_text.text != "" if @debug_text.text != ""
$window.draw_rect(@debug_text.x - 10, (@debug_text.y - 10), @debug_text.width + 20, @debug_text.height + 20, Gosu.draw_rect(@debug_text.x - 10, (@debug_text.y - 10), @debug_text.width + 20, @debug_text.height + 20,
Gosu::Color.rgba(0, 0, 0, 200), 9999) Gosu::Color.rgba(0, 0, 0, 200), 9999)
@debug_text.draw @debug_text.draw
end end
@@ -102,13 +102,13 @@ module CyberarmEngine
end end
def _x_visible def _x_visible
x.between?(($window.width / 2) - @world_center_point.x, ($window.width / 2) + @world_center_point.x) || x.between?((window.width / 2) - @world_center_point.x, (window.width / 2) + @world_center_point.x) ||
x.between?((@world_center_point.x - $window.width / 2), ($window.width / 2) + @world_center_point.x) x.between?((@world_center_point.x - window.width / 2), (window.width / 2) + @world_center_point.x)
end end
def _y_visible def _y_visible
y.between?(($window.height / 2) - @world_center_point.y, ($window.height / 2) + @world_center_point.y) || y.between?((window.height / 2) - @world_center_point.y, (window.height / 2) + @world_center_point.y) ||
y.between?(@world_center_point.y - ($window.height / 2), ($window.height / 2) + @world_center_point.y) y.between?(@world_center_point.y - (window.height / 2), (window.height / 2) + @world_center_point.y)
end end
def heading(ahead_by = 100, _object = nil, angle_only = false) def heading(ahead_by = 100, _object = nil, angle_only = false)
@@ -153,10 +153,6 @@ module CyberarmEngine
@color = Gosu::Color.rgba(@color.red, @color.green, @color.blue, int) @color = Gosu::Color.rgba(@color.red, @color.green, @color.blue, int)
end end
def draw_rect(x, y, width, height, color, z = 0)
$window.draw_rect(x, y, width, height, color, z)
end
def button_up(id) def button_up(id)
end end
@@ -190,12 +186,12 @@ module CyberarmEngine
# Duplication... so DRY. # Duplication... so DRY.
def each_circle_collision(object, _resolve_with = :width, &block) def each_circle_collision(object, _resolve_with = :width, &block)
if object.class != Class && object.instance_of?(object.class) if object.class != Class && object.instance_of?(object.class)
$window.current_state.game_objects.select { |i| i.instance_of?(object.class) }.each do |o| window.current_state.game_objects.select { |i| i.instance_of?(object.class) }.each do |o|
distance = Gosu.distance(x, y, object.x, object.y) distance = Gosu.distance(x, y, object.x, object.y)
block.call(o, object) if distance <= radius + object.radius && block block.call(o, object) if distance <= radius + object.radius && block
end end
else else
list = $window.current_state.game_objects.select { |i| i.instance_of?(object) } list = window.current_state.game_objects.select { |i| i.instance_of?(object) }
list.each do |o| list.each do |o|
next if self == o next if self == o
@@ -206,9 +202,9 @@ module CyberarmEngine
end end
def destroy def destroy
if $window.current_state if window.current_state
$window.current_state.game_objects.each do |o| window.current_state.game_objects.each do |o|
$window.current_state.game_objects.delete(o) if o.is_a?(self.class) && o == self window.current_state.game_objects.delete(o) if o.is_a?(self.class) && o == self
end end
end end
end end
@@ -220,13 +216,13 @@ module CyberarmEngine
def self.each_circle_collision(object, _resolve_with = :width, &block) def self.each_circle_collision(object, _resolve_with = :width, &block)
if object.class != Class && object.instance_of?(object.class) if object.class != Class && object.instance_of?(object.class)
$window.current_state.game_objects.select { |i| i.instance_of?(self) }.each do |o| window.current_state.game_objects.select { |i| i.instance_of?(self) }.each do |o|
distance = Gosu.distance(o.x, o.y, object.x, object.y) distance = Gosu.distance(o.x, o.y, object.x, object.y)
block.call(o, object) if distance <= o.radius + object.radius && block block.call(o, object) if distance <= o.radius + object.radius && block
end end
else else
lista = $window.current_state.game_objects.select { |i| i.instance_of?(self) } lista = window.current_state.game_objects.select { |i| i.instance_of?(self) }
listb = $window.current_state.game_objects.select { |i| i.instance_of?(object) } listb = window.current_state.game_objects.select { |i| i.instance_of?(object) }
lista.product(listb).each do |o, o2| lista.product(listb).each do |o, o2|
next if o == o2 next if o == o2
@@ -238,9 +234,9 @@ module CyberarmEngine
def self.destroy_all def self.destroy_all
INSTANCES.clear INSTANCES.clear
if $window.current_state if window.current_state
$window.current_state.game_objects.each do |o| window.current_state.game_objects.each do |o|
$window.current_state.game_objects.delete(o) if o.is_a?(self.class) window.current_state.game_objects.delete(o) if o.is_a?(self.class)
end end
end end
end end

View File

@@ -9,7 +9,7 @@ module CyberarmEngine
@options = options @options = options
@game_objects = [] @game_objects = []
@global_pause = false @global_pause = false
$window.text_input = nil unless options[:preserve_text_input] window.text_input = nil unless options[:preserve_text_input]
@down_keys = {} @down_keys = {}
end end
@@ -30,48 +30,27 @@ module CyberarmEngine
@game_objects.each(&:update) @game_objects.each(&:update)
end end
def draw_bounding_box(box) def needs_redraw?
x = box.x true
y = box.y
max_x = box.max_x
max_y = box.max_y
color = Gosu::Color.rgba(255, 127, 64, 240)
# pipe = 4
# Gosu.draw_rect(x-width, y-height, x+(width*2), y+(height*2), color, Float::INFINITY)
# puts "BB render: #{x}:#{y} w:#{x.abs+width} h:#{y.abs+height}"
# Gosu.draw_rect(x, y, x.abs+width, y.abs+height, color, Float::INFINITY)
# TOP LEFT to BOTTOM LEFT
$window.draw_line(
x, y, color,
x, max_y, color,
Float::INFINITY
)
# BOTTOM LEFT to BOTTOM RIGHT
$window.draw_line(
x, max_y, color,
max_x, max_y, color,
Float::INFINITY
)
# BOTTOM RIGHT to TOP RIGHT
$window.draw_line(
max_x, max_y, color,
max_x, y, color,
Float::INFINITY
)
# TOP RIGHT to TOP LEFT
$window.draw_line(
max_x, y, color,
x, y, color,
Float::INFINITY
)
end end
def destroy def needs_repaint?
@options.clear true
@game_objects.clear end
def drop(filename)
end
def gamepad_connected(index)
end
def gamepad_disconnected(index)
end
def gain_focus
end
def lose_focus
end end
def button_down(id) def button_down(id)
@@ -90,6 +69,54 @@ module CyberarmEngine
end end
end end
def close
window.close!
end
def draw_bounding_box(box)
x = box.x
y = box.y
max_x = box.max_x
max_y = box.max_y
color = Gosu::Color.rgba(255, 127, 64, 240)
# pipe = 4
# Gosu.draw_rect(x-width, y-height, x+(width*2), y+(height*2), color, Float::INFINITY)
# puts "BB render: #{x}:#{y} w:#{x.abs+width} h:#{y.abs+height}"
# Gosu.draw_rect(x, y, x.abs+width, y.abs+height, color, Float::INFINITY)
# TOP LEFT to BOTTOM LEFT
Gosu.draw_line(
x, y, color,
x, max_y, color,
Float::INFINITY
)
# BOTTOM LEFT to BOTTOM RIGHT
Gosu.draw_line(
x, max_y, color,
max_x, max_y, color,
Float::INFINITY
)
# BOTTOM RIGHT to TOP RIGHT
Gosu.draw_line(
max_x, max_y, color,
max_x, y, color,
Float::INFINITY
)
# TOP RIGHT to TOP LEFT
Gosu.draw_line(
max_x, y, color,
x, y, color,
Float::INFINITY
)
end
def destroy
@options.clear
@game_objects.clear
end
def add_game_object(object) def add_game_object(object)
@game_objects << object @game_objects << object
end end

View File

@@ -49,8 +49,9 @@ module CyberarmEngine
@objects.each { |o| @vertex_count += o.vertices.size } @objects.each { |o| @vertex_count += o.vertices.size }
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) # start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
# build_collision_tree # build_collision_tree
# puts " Building mesh collision tree took #{((Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time) / 1000.0).round(2)} seconds"
end end
def parse(parser) def parse(parser)
@@ -150,24 +151,24 @@ module CyberarmEngine
glBindBuffer(GL_ARRAY_BUFFER, @positions_buffer_id) glBindBuffer(GL_ARRAY_BUFFER, @positions_buffer_id)
gl_error? gl_error?
# inPosition # in_position
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nil) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error? gl_error?
# colors # colors
glBindBuffer(GL_ARRAY_BUFFER, @colors_buffer_id) glBindBuffer(GL_ARRAY_BUFFER, @colors_buffer_id)
# inColor # in_color
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nil) glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error? gl_error?
# normals # normals
glBindBuffer(GL_ARRAY_BUFFER, @normals_buffer_id) glBindBuffer(GL_ARRAY_BUFFER, @normals_buffer_id)
# inNormal # in_normal
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, nil) glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, nil)
gl_error? gl_error?
if has_texture? if has_texture?
# uvs # uvs
glBindBuffer(GL_ARRAY_BUFFER, @uvs_buffer_id) glBindBuffer(GL_ARRAY_BUFFER, @uvs_buffer_id)
# inUV # in_uv
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, nil) glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error? gl_error?
end end
@@ -177,7 +178,7 @@ module CyberarmEngine
end end
def build_collision_tree def build_collision_tree
@aabb_tree = AABBTree.new @aabb_tree = IMICFPS::AABBTree.new
@faces.each do |face| @faces.each do |face|
box = BoundingBox.new box = BoundingBox.new

View File

@@ -11,7 +11,19 @@ module CyberarmEngine
if e != GL_NO_ERROR if e != GL_NO_ERROR
warn "OpenGL error detected by handler at: #{caller[0]}" warn "OpenGL error detected by handler at: #{caller[0]}"
warn " #{gluErrorString(e)} (#{e})\n" warn " #{gluErrorString(e)} (#{e})\n"
exit if window.exit_on_opengl_error? exit if Window.instance&.exit_on_opengl_error?
end
end
def preload_default_shaders
shaders = %w[g_buffer lighting]
shaders.each do |shader|
Shader.new(
name: shader,
includes_dir: "#{CYBERARM_ENGINE_ROOT_PATH}/assets/shaders/include",
vertex: "#{CYBERARM_ENGINE_ROOT_PATH}/assets/shaders/vertex/#{shader}.glsl",
fragment: "#{CYBERARM_ENGINE_ROOT_PATH}/assets/shaders/fragment/#{shader}.glsl"
)
end end
end end
end end

View File

@@ -1,6 +1,7 @@
module CyberarmEngine module CyberarmEngine
class GBuffer class GBuffer
attr_reader :screen_vbo, :vertices, :uvs attr_reader :screen_vbo, :vertices, :uvs
attr_reader :width, :height
def initialize(width:, height:) def initialize(width:, height:)
@width = width @width = width

View File

@@ -44,7 +44,7 @@ module CyberarmEngine
shader.uniform_transform("projection", camera.projection_matrix) shader.uniform_transform("projection", camera.projection_matrix)
shader.uniform_transform("view", camera.view_matrix) shader.uniform_transform("view", camera.view_matrix)
shader.uniform_transform("model", entity.model_matrix) shader.uniform_transform("model", entity.model_matrix)
shader.uniform_vec3("cameraPosition", camera.position) shader.uniform_vector3("camera_position", camera.position)
gl_error? gl_error?
draw_model(entity.model, shader) draw_model(entity.model, shader)
@@ -154,16 +154,22 @@ module CyberarmEngine
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:depth)) glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:depth))
shader.uniform_integer("depth", 4) shader.uniform_integer("depth", 4)
lights.each_with_index do |light, _i| # FIXME: Try to figure out how to up this to 32 and/or beyond
shader.uniform_integer("light[0].type", light.type) # (currently fails with more then 7 lights passed in to shader)
shader.uniform_vec3("light[0].direction", light.direction) lights.each_slice(7).each do |light_group|
shader.uniform_vec3("light[0].position", light.position) light_group.each_with_index do |light, _i|
shader.uniform_vec3("light[0].diffuse", light.diffuse) shader.uniform_integer("light_count", light_group.size)
shader.uniform_vec3("light[0].ambient", light.ambient)
shader.uniform_vec3("light[0].specular", light.specular) shader.uniform_integer("lights[#{_i}].type", light.type)
shader.uniform_vector3("lights[#{_i}].direction", light.direction)
shader.uniform_vector3("lights[#{_i}].position", light.position)
shader.uniform_vector3("lights[#{_i}].diffuse", light.diffuse)
shader.uniform_vector3("lights[#{_i}].ambient", light.ambient)
shader.uniform_vector3("lights[#{_i}].specular", light.specular)
glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size) glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size)
end end
end
glBindVertexArray(0) glBindVertexArray(0)
end end
@@ -215,7 +221,7 @@ module CyberarmEngine
offset = 0 offset = 0
model.objects.each do |object| model.objects.each do |object|
shader.uniform_boolean("hasTexture", object.has_texture?) shader.uniform_boolean("has_texture", object.has_texture?)
if object.has_texture? if object.has_texture?
glBindTexture(GL_TEXTURE_2D, object.materials.find { |mat| mat.texture_id }.texture_id) glBindTexture(GL_TEXTURE_2D, object.materials.find { |mat| mat.texture_id }.texture_id)

View File

@@ -4,12 +4,23 @@ module CyberarmEngine
def initialize def initialize
@bounding_box_renderer = BoundingBoxRenderer.new @bounding_box_renderer = BoundingBoxRenderer.new
@opengl_renderer = OpenGLRenderer.new(width: $window.width, height: $window.height) @opengl_renderer = OpenGLRenderer.new(width: CyberarmEngine::Window.instance.width, height: CyberarmEngine::Window.instance.height)
end end
def draw(camera, lights, entities) def draw(camera, lights, entities)
Stats.frame.start_timing(:opengl_renderer)
Stats.frame.start_timing(:opengl_model_renderer)
@opengl_renderer.render(camera, lights, entities) @opengl_renderer.render(camera, lights, entities)
@bounding_box_renderer.render(entities) if @show_bounding_boxes Stats.frame.end_timing(:opengl_model_renderer)
if @show_bounding_boxes
Stats.frame.start_timing(:opengl_boundingbox_renderer)
@bounding_box_renderer.render(entities)
Stats.frame.end_timing(:opengl_boundingbox_renderer)
end
Stats.frame.end_timing(:opengl_renderer)
end end
def canvas_size_changed def canvas_size_changed

View File

@@ -385,7 +385,7 @@ module CyberarmEngine
# @param value [Vector] # @param value [Vector]
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_vec3(variable, value, location = nil) def uniform_vector3(variable, value, location = nil)
attr_loc = location || attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniform3f(attr_loc, *value.to_a[0..2]) glUniform3f(attr_loc, *value.to_a[0..2])
@@ -397,7 +397,7 @@ module CyberarmEngine
# @param value [Vector] # @param value [Vector]
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_vec4(variable, value, location = nil) def uniform_vector4(variable, value, location = nil)
attr_loc = location || attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniform4f(attr_loc, *value.to_a) glUniform4f(attr_loc, *value.to_a)

View File

@@ -1,20 +1,191 @@
module CyberarmEngine module CyberarmEngine
class Stats class Stats
@@hash = { @frames = []
gui_recalculations_last_frame: 0 @frame_index = -1
@max_frame_history = 1024
def self.new_frame
if @frames.size < @max_frame_history
@frames << Frame.new
else
@frames[@frame_index] = Frame.new
end
end
def self.frame
@frames[@frame_index]
end
def self.end_frame
frame&.complete
@frame_index += 1
@frame_index %= @max_frame_history
end
def self.frames
if @frames.size < @max_frame_history
@frames
else
@frames.rotate(@frame_index - (@max_frame_history - (@frames.size - 1)))
end
end
def self.frame_index
@frame_index
end
def self.max_frame_history
@max_frame_history
end
class Frame
Timing = Struct.new(:start_time, :end_time, :duration)
attr_reader :frame_timing, :counters, :timings, :multitimings
def initialize
@frame_timing = Timing.new(Gosu.milliseconds, -1, -1)
@attempted_multitiming = false
@counters = {
gui_recalculations: 0
} }
def self.get(key) @timings = {}
@@hash.dig(key) @multitimings = {}
end end
def self.increment(key, n) def increment(key, number = 1)
@@hash[key] += n @counters[key] ||= 0
@counters[key] += number
end end
def self.clear def start_timing(key)
@@hash.each do |key, _value| raise "key must be a symbol!" unless key.is_a?(Symbol)
@@hash[key] = 0 if @timings[key]
# FIXME: Make it not spammy...
# warn "Only one timing per key per frame. (Timing for #{key.inspect} already exists!)"
@attempted_multitiming = true
@multitimings[key] = true
return
end
@timings[key] = Timing.new(Gosu.milliseconds, -1, -1)
end
def end_timing(key)
timing = @timings[key]
# FIXME: Make it not spammy...
# warn "Timing #{key.inspect} already ended!" if timing.end_time != -1
timing.end_time = Gosu.milliseconds
timing.duration = timing.end_time - timing.start_time
end
def complete
@frame_timing.end_time = Gosu.milliseconds
@frame_timing.duration = @frame_timing.end_time - @frame_timing.start_time
# Lock data structures
@frame_timing.freeze
@counters.freeze
@timings.freeze
@multitimings.freeze
end
def complete?
@frame_timing.duration != -1
end
def attempted_multitiming?
@attempted_multitiming
end
end
class StatsPlotter
attr_reader :position
def initialize(x, y, z = Float::INFINITY, width = 128, height = 128)
@position = Vector.new(x, y, z)
@width = width
@height = height
@padding = 2
@text_size = 16
@max_timing_label = CyberarmEngine::Text.new("", x: x + @padding + 1, y: y + @padding, z: z, size: @text_size, border: true)
@avg_timing_label = CyberarmEngine::Text.new("", x: x + @padding + 1, y: y + @padding + @height / 2 - @text_size / 2, z: z, size: @text_size, border: true)
@min_timing_label = CyberarmEngine::Text.new("", x: x + @padding + 1, y: y + @height - (@text_size + @padding / 2), z: z, size: @text_size, border: true)
@timings_label = CyberarmEngine::Text.new("", x: x + @padding + @width + @padding, y: y + @padding, z: z, size: @text_size, border: true)
@frame_stats = []
@graphs = {
frame_timings: []
}
end
def calculate_graphs
calculate_frame_timings_graph
end
def calculate_frame_timings_graph
@graphs[:frame_timings].clear
samples = @width - @padding
nodes = Array.new(samples.ceil) { [] }
slice = 0
@frame_stats.each_slice((CyberarmEngine::Stats.max_frame_history / samples.to_f).ceil) do |bucket|
bucket.each do |frame|
nodes[slice] << frame.frame_timing.duration
end
slice += 1
end
nodes.each_with_index do |cluster, i|
break if cluster.empty?
@graphs[:frame_timings] << CyberarmEngine::Vector.new(@position.x + @padding + 1 * i, (@position.y + @height - @padding) - cluster.max)
end
end
def draw
@frame_stats = CyberarmEngine::Stats.frames.select(&:complete?)
return if @frame_stats.empty?
calculate_graphs
@max_timing_label.text = "Max: #{@frame_stats.map { |f| f.frame_timing.duration }.max.to_s.rjust(3, " ")}ms"
@avg_timing_label.text = "Avg: #{(@frame_stats.map { |f| f.frame_timing.duration }.sum / @frame_stats.size).to_s.rjust(3, " ")}ms"
@min_timing_label.text = "Min: #{@frame_stats.map { |f| f.frame_timing.duration }.min.to_s.rjust(3, " ")}ms"
Gosu.draw_rect(@position.x, @position.y, @width, @height, 0xaa_222222, @position.z)
Gosu.draw_rect(@position.x + @padding, @position.y + @padding, @width - @padding * 2, @height - @padding * 2, 0xaa_222222, @position.z)
draw_graphs
@max_timing_label.draw
@avg_timing_label.draw
@min_timing_label.draw
# TODO: Make this optional
draw_timings
end
def draw_graphs
Gosu.draw_path(@graphs[:frame_timings], Gosu::Color::WHITE, Float::INFINITY)
end
def draw_timings
frame = @frame_stats.last
@timings_label.text = "#{frame.attempted_multitiming? ? "<c=d00>Attempted Multitiming!\nTimings may be inaccurate for:\n#{frame.multitimings.map { |m, _| m}.join("\n") }</c>\n\n" : ''}#{frame.timings.map { |t, v| "#{t}: #{v.duration}ms" }.join("\n")}"
Gosu.draw_rect(@timings_label.x - @padding, @timings_label.y - @padding, @timings_label.width + @padding * 2, @timings_label.height + @padding * 2, 0xdd_222222, @position.z)
@timings_label.draw
end end
end end
end end

View File

@@ -29,12 +29,14 @@ module CyberarmEngine
@border = true if options[:border].nil? @border = true if options[:border].nil?
@border_size = options[:border_size] || 1 @border_size = options[:border_size] || 1
@border_alpha = options[:border_alpha] || 30 @border_alpha = options[:border_alpha] || 30
@border_color = options[:border_color] @border_color = options[:border_color] || Gosu::Color::BLACK
@shadow = options[:shadow] @shadow = options[:shadow]
@shadow_size = options[:shadow_size] || 2 @shadow_size = options[:shadow_size] || 2
@shadow_alpha = options[:shadow_alpha] || 30 @shadow_alpha = options[:shadow_alpha] || 30
@shadow_color = options[:shadow_color] @shadow_color = options[:shadow_color] || Gosu::Color::BLACK
@static = options[:static] || (options[:static].nil? || options[:static] == false ? false : true)
@textobject = check_cache(@size, @font) @textobject = check_cache(@size, @font)
@@ -43,9 +45,9 @@ module CyberarmEngine
when :left when :left
@x = 0 + BUTTON_PADDING @x = 0 + BUTTON_PADDING
when :center when :center
@x = ($window.width / 2) - (@textobject.text_width(@text) / 2) @x = (CyberarmEngine::Window.instance.width / 2) - (@textobject.text_width(@text) / 2)
when :right when :right
@x = $window.width - BUTTON_PADDING - @textobject.text_width(@text) @x = CyberarmEngine::Window.instance.width - BUTTON_PADDING - @textobject.text_width(@text)
end end
end end
end end
@@ -79,51 +81,55 @@ module CyberarmEngine
@size = size @size = size
@font = font_name @font = font_name
invalidate_cache!
@textobject = check_cache(size, font_name) @textobject = check_cache(size, font_name)
end end
end end
def text=(string) def text=(string)
@rendered_border = nil invalidate_cache! if @text != string
@text = string @text = string
end end
def factor_x=(n) def factor_x=(n)
@rendered_border = nil invalidate_cache! if @factor_x != n
@factor_x = n @factor_x = n
end end
def factor_y=(n) def factor_y=(n)
@rendered_border = nil invalidate_cache! if @factor_y != n
@factor_y = n @factor_y = n
end end
def color=(color) def color=(color)
@rendered_border = nil old_color = @color
if color if color
@color = color.is_a?(Gosu::Color) ? color : Gosu::Color.new(color) @color = color.is_a?(Gosu::Color) ? color : Gosu::Color.new(color)
else else
raise "color cannot be nil" raise "color cannot be nil"
end end
invalidate_cache! if old_color != color
end end
def border=(boolean) def border=(boolean)
@rendered_border = nil invalidate_cache! if @border != boolean
@border = boolean @border = boolean
end end
def border_size=(n) def border_size=(n)
@rendered_border = nil invalidate_cache! if @border_size != n
@border_size = n @border_size = n
end end
def border_alpha=(n) def border_alpha=(n)
@rendered_border = nil invalidate_cache! if @border_alpha != n
@border_alpha = n @border_alpha = n
end end
def border_color=(n) def border_color=(n)
@rendered_border = nil invalidate_cache! if @border_color != n
@border_color = n @border_color = n
end end
@@ -132,11 +138,29 @@ module CyberarmEngine
end end
def text_width(text = @text) def text_width(text = @text)
textobject.text_width(text) + @border_size + @shadow_size spacing = 0
spacing += @border_size if @border
spacing += @shadow_size if @shadow
if text == @text && @static && @gosu_cached_text_image
@gosu_cached_text_image&.width + spacing
else
textobject.text_width(text) + spacing
end
end end
def markup_width(text = @text) def markup_width(text = @text)
textobject.markup_width(text) + @border_size + @shadow_size text = text.to_s
spacing = 0
spacing += @border_size if @border
spacing += @shadow_size if @shadow
if text == @text && @static && @gosu_cached_text_image
@gosu_cached_text_image&.width + spacing
else
textobject.markup_width(text) + spacing
end
end end
def height(text = @text) def height(text = @text)
@@ -148,6 +172,38 @@ module CyberarmEngine
end end
def draw(method = :draw_markup) def draw(method = :draw_markup)
if @static
if @border && !@cached_text_border_image
_x = @border_size
_y = @border_size
_width = method == :draw_markup ? text_width : markup_width
img = Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font)
@cached_text_border_image = Gosu.render((_width + (@border_size * 2)).ceil, (height + (@border_size * 2)).ceil) do
img.draw(-_x, 0, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(-_x, -_y, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(0, -_y, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(_x, -_y, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(_x, 0, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(_x, _y, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(0, _y, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(-_x, _y, @z, @factor_x, @factor_y, @border_color, @mode)
end
end
@cached_text_shadow_image ||= Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font) if @shadow
@gosu_cached_text_image ||= Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font)
@cached_text_border_image.draw(@x, @y, @z, @factor_x, @factor_y, @border_color, @mode) if @border
@cached_text_shadow_image.draw(@x + @shadow_size, @y + @shadow_size, @z, @factor_x, @factor_y, @shadow_color, @mode) if @shadow
@gosu_cached_text_image.draw(@x, @y, @z, @factor_x, @factor_y, @color, @mode)
else
if @border && !ARGV.join.include?("--no-border") if @border && !ARGV.join.include?("--no-border")
border_alpha = @color.alpha <= 30 ? @color.alpha : @border_alpha border_alpha = @color.alpha <= 30 ? @color.alpha : @border_alpha
border_color = @border_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue, border_color = @border_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue,
@@ -158,7 +214,7 @@ module CyberarmEngine
_y = @border_size _y = @border_size
_width = method == :draw_markup ? text_width : markup_width _width = method == :draw_markup ? text_width : markup_width
@rendered_border ||= Gosu.render((_width + (border_size * 2)).ceil, (height + (@border_size * 2)).ceil) do @cached_text_border_image ||= Gosu.render((_width + (border_size * 2)).ceil, (height + (@border_size * 2)).ceil) do
@textobject.send(method, @text, _x - @border_size, _y, @z, @factor_x, @factor_y, white, @mode) @textobject.send(method, @text, _x - @border_size, _y, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x - @border_size, _y - @border_size, @z, @factor_x, @factor_y, white, @mode) @textobject.send(method, @text, _x - @border_size, _y - @border_size, @z, @factor_x, @factor_y, white, @mode)
@@ -172,7 +228,7 @@ module CyberarmEngine
@textobject.send(method, @text, _x + @border_size, _y + @border_size, @z, @factor_x, @factor_y, white, @mode) @textobject.send(method, @text, _x + @border_size, _y + @border_size, @z, @factor_x, @factor_y, white, @mode)
end end
@rendered_border.draw(@x - @border_size, @y - @border_size, @z, @factor_x, @factor_y, border_color) @cached_text_border_image.draw(@x - @border_size, @y - @border_size, @z, @factor_x, @factor_y, border_color)
end end
if @shadow if @shadow
@@ -182,6 +238,7 @@ module CyberarmEngine
@textobject.send(method, @text, @x, @y, @z, @factor_x, @factor_y, @color, @mode) @textobject.send(method, @text, @x, @y, @z, @factor_x, @factor_y, @color, @mode)
end end
end
def alpha=(n) def alpha=(n)
@color = Gosu::Color.rgba(@color.red, @color.green, @color.blue, n) @color = Gosu::Color.rgba(@color.red, @color.green, @color.blue, n)
@@ -193,5 +250,11 @@ module CyberarmEngine
def update def update
end end
def invalidate_cache!
@cached_text_border_image = nil
@cached_text_shadow_image = nil
@gosu_cached_text_image = nil
end
end end
end end

View File

@@ -62,11 +62,11 @@ module CyberarmEngine
def update def update
# TOP # TOP
@top.x = @element.x # + @element.border_thickness_left @top.x = @element.x + @element.style.border_thickness_left
@top.y = @element.y @top.y = @element.y
@top.z = @element.z @top.z = @element.z
@top.width = @element.width @top.width = @element.width - @element.style.border_thickness_left
@top.height = @element.style.border_thickness_top @top.height = @element.style.border_thickness_top
# RIGHT # RIGHT

View File

@@ -116,7 +116,7 @@ module CyberarmEngine
end end
private def element_parent private def element_parent
$__current_container__ CyberarmEngine::Element::Container.current_container
end end
private def container(klass, options = {}, &block) private def container(klass, options = {}, &block)
@@ -126,12 +126,12 @@ module CyberarmEngine
_container = klass.new(options, block) _container = klass.new(options, block)
old_parent = element_parent old_parent = element_parent
$__current_container__ = _container CyberarmEngine::Element::Container.current_container = _container
_container.build _container.build
_container.parent.add(_container) _container.parent.add(_container)
$__current_container__ = old_parent CyberarmEngine::Element::Container.current_container = old_parent
_container _container
end end

View File

@@ -38,6 +38,7 @@ module CyberarmEngine
@style.background_canvas = Background.new @style.background_canvas = Background.new
@style.background_nine_slice_canvas = BackgroundNineSlice.new @style.background_nine_slice_canvas = BackgroundNineSlice.new
@style.background_image_canvas = BackgroundImage.new
@style.border_canvas = BorderCanvas.new(element: self) @style.border_canvas = BorderCanvas.new(element: self)
@style_event = :default @style_event = :default
@@ -60,9 +61,12 @@ module CyberarmEngine
set_background set_background
set_background_nine_slice set_background_nine_slice
set_background_image
set_border_thickness set_border_thickness
set_border_color set_border_color
root.gui_state.request_repaint
end end
def safe_style_fetch(*args) def safe_style_fetch(*args)
@@ -104,6 +108,14 @@ module CyberarmEngine
@style.background_nine_slice_bottom = safe_style_fetch(:background_nine_slice_bottom) || @style.background_nine_slice_from_edge @style.background_nine_slice_bottom = safe_style_fetch(:background_nine_slice_bottom) || @style.background_nine_slice_from_edge
end end
def set_background_image
@style.background_image = safe_style_fetch(:background_image)
@style.background_image_mode = safe_style_fetch(:background_image_mode) || :stretch
@style.background_image_color = safe_style_fetch(:background_image_color) || Gosu::Color::WHITE
@style.background_image_canvas.mode = @style.background_image_mode
@style.background_image_canvas.color = @style.background_image_color
end
def set_border_thickness def set_border_thickness
@style.border_thickness = safe_style_fetch(:border_thickness) @style.border_thickness = safe_style_fetch(:border_thickness)
@@ -156,10 +168,10 @@ module CyberarmEngine
return if self.is_a?(ToolTip) return if self.is_a?(ToolTip)
if old_width != width || old_height != height if old_width != width || old_height != height
(root&.gui_state || @gui_state).request_recalculate root.gui_state.request_recalculate
else
stylize
end end
stylize
end end
def default_events def default_events
@@ -184,7 +196,7 @@ module CyberarmEngine
end end
def enter(_sender) def enter(_sender)
@focus = false unless window.button_down?(Gosu::MsLeft) @focus = false unless Gosu.button_down?(Gosu::MS_LEFT)
if !@enabled if !@enabled
update_styles(:disabled) update_styles(:disabled)
@@ -246,6 +258,8 @@ module CyberarmEngine
end end
def enabled=(boolean) def enabled=(boolean)
root.gui_state.request_repaint if @enabled != boolean
@enabled = boolean @enabled = boolean
recalculate recalculate
@@ -257,6 +271,10 @@ module CyberarmEngine
@enabled @enabled
end end
def focused?
@focus
end
def visible? def visible?
@visible @visible
end end
@@ -268,18 +286,21 @@ module CyberarmEngine
def toggle def toggle
@visible = !@visible @visible = !@visible
root.gui_state.request_recalculate root.gui_state.request_recalculate
root.gui_state.request_repaint
end end
def show def show
bool = visible? bool = visible?
@visible = true @visible = true
root.gui_state.request_recalculate unless bool root.gui_state.request_recalculate unless bool
root.gui_state.request_repaint unless bool
end end
def hide def hide
bool = visible? bool = visible?
@visible = false @visible = false
root.gui_state.request_recalculate if bool root.gui_state.request_recalculate if bool
root.gui_state.request_repaint if bool
end end
def draw def draw
@@ -288,13 +309,15 @@ module CyberarmEngine
@style.background_canvas.draw @style.background_canvas.draw
@style.background_nine_slice_canvas.draw @style.background_nine_slice_canvas.draw
@style.background_image_canvas.draw
@style.border_canvas.draw @style.border_canvas.draw
render render
end end
def debug_draw def debug_draw
return if defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT # FIXME
return# if const_defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT
Gosu.draw_line( Gosu.draw_line(
x, y, @debug_color, x, y, @debug_color,
@@ -388,10 +411,14 @@ module CyberarmEngine
end end
def scroll_width def scroll_width
@children.sum(&:outer_width) return @cached_scroll_width if @cached_scroll_width && is_a?(Container)
@cached_scroll_width = @children.sum(&:outer_width)
end end
def scroll_height def scroll_height
return @cached_scroll_height if @cached_scroll_height && is_a?(Container)
if is_a?(CyberarmEngine::Element::Flow) if is_a?(CyberarmEngine::Element::Flow)
return 0 if @children.size.zero? return 0 if @children.size.zero?
@@ -412,31 +439,62 @@ module CyberarmEngine
pairs_ << a_ unless pairs_.last == a_ pairs_ << a_ unless pairs_.last == a_
pairs_.sum { |pair| pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom @cached_scroll_height = pairs_.sum { |pair| + @style.padding_top + @style.border_thickness_top + pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom
else else
@children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom @cached_scroll_height = @style.padding_top + @style.border_thickness_top + @children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom
end end
end end
def max_scroll_width def max_scroll_width
scroll_width - outer_width (scroll_width - outer_width).positive? ? scroll_width - outer_width : scroll_width
end end
def max_scroll_height def max_scroll_height
scroll_height - outer_height (scroll_height - outer_height).positive? ? scroll_height - outer_height : scroll_height
end end
def dimensional_size(size, dimension) def dimensional_size(size, dimension)
raise "dimension must be either :width or :height" unless %i[width height].include?(dimension) raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
if size.is_a?(Numeric) && size.between?(0.0, 1.0) new_size = if size.is_a?(Float) && size.between?(0.0, 1.0)
(@parent.send(:"content_#{dimension}") * size).round - send(:"noncontent_#{dimension}").round (@parent.send(:"content_#{dimension}") * size).floor - send(:"noncontent_#{dimension}").floor
else else
size size
end end
# Handle fill behavior
if @parent && @style.fill &&
(dimension == :width && @parent.is_a?(Flow) ||
dimension == :height && @parent.is_a?(Stack))
new_size = space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow)
new_size = space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack)
end
return @style.send(:"min_#{dimension}") if @style.send(:"min_#{dimension}") && new_size.to_f < @style.send(:"min_#{dimension}")
return @style.send(:"max_#{dimension}") if @style.send(:"max_#{dimension}") && new_size.to_f > @style.send(:"max_#{dimension}")
new_size
end
def space_available_width
# TODO: This may get expensive if there are a lot of children, probably should cache it somehow
fill_siblings = @parent.children.select { |c| c.style.fill }.count.to_f # include self since we're dividing
available_space = ((@parent.content_width - (@parent.children.reject { |c| c.style.fill }).map(&:outer_width).sum) / fill_siblings)
(available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
end
def space_available_height
# TODO: This may get expensive if there are a lot of children, probably should cache it somehow
fill_siblings = @parent.children.select { |c| c.style.fill }.count.to_f # include self since we're dividing
available_space = ((@parent.content_height - (@parent.children.reject { |c| c.style.fill }).map(&:outer_height).sum) / fill_siblings)
(available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
end end
def background=(_background) def background=(_background)
root.gui_state.request_repaint
@style.background_canvas.background = _background @style.background_canvas.background = _background
update_background update_background
end end
@@ -450,10 +508,13 @@ module CyberarmEngine
@style.background_canvas.update @style.background_canvas.update
update_background_nine_slice update_background_nine_slice
update_background_image
@style.border_canvas.update @style.border_canvas.update
end end
def background_nine_slice=(_image_path) def background_nine_slice=(_image_path)
root.gui_state.request_repaint
@style.background_nine_slice_canvas.image = _image_path @style.background_nine_slice_canvas.image = _image_path
update_background_nine_slice update_background_nine_slice
end end
@@ -477,6 +538,26 @@ module CyberarmEngine
@style.background_nine_slice_canvas.image = @style.background_nine_slice @style.background_nine_slice_canvas.image = @style.background_nine_slice
end end
def background_image=(image_path)
root.gui_state.request_repaint
@style.background_image = image_path.is_a?(Gosu::Image) ? image_path : get_image(image_path)
update_background_image
end
def update_background_image
@style.background_image_canvas.x = @x
@style.background_image_canvas.y = @y
@style.background_image_canvas.z = @z
@style.background_image_canvas.width = width
@style.background_image_canvas.height = height
@style.background_image_canvas.mode = @style.background_image_mode
@style.background_image_canvas.color = @style.background_image_color
@style.background_image_canvas.image = @style.background_image
end
def root def root
return self if is_root? return self if is_root?
@@ -497,6 +578,20 @@ module CyberarmEngine
@gui_state != nil @gui_state != nil
end end
def child_of?(element)
return element == self if is_root?
return false unless element.is_a?(Container)
return true if element.children.find { |child| child == self }
element.children.find { |child| child.child_of?(element) if child.is_a?(Container) }
end
# def parent_of?(element)
# return true if element.children.find { |child| child == self }
# element.children.find { |child| child.parent}
# end
def focus(_) def focus(_)
warn "#{self.class}#focus was not overridden!" warn "#{self.class}#focus was not overridden!"

View File

@@ -64,8 +64,8 @@ module CyberarmEngine
@scale_y = 1 @scale_y = 1
end end
@width = _width || @image.width.round * @scale_x @width = _width || @image.width.floor * @scale_x
@height = _height || @image.height.round * @scale_y @height = _height || @image.height.floor * @scale_y
update_background update_background
else else

View File

@@ -6,11 +6,24 @@ module CyberarmEngine
attr_accessor :stroke_color, :fill_color attr_accessor :stroke_color, :fill_color
attr_reader :children, :gui_state, :scroll_position attr_reader :children, :gui_state, :scroll_position
def self.current_container
@@current_container
end
def self.current_container=(container)
raise ArgumentError, "Expected container to an an instance of CyberarmEngine::Element::Container, got #{container.class}" unless container.is_a?(CyberarmEngine::Element::Container)
@@current_container = container
end
def initialize(options = {}, block = nil) def initialize(options = {}, block = nil)
@gui_state = options.delete(:gui_state) @gui_state = options.delete(:gui_state)
super super
@last_scroll_position = Vector.new(0, 0)
@scroll_position = Vector.new(0, 0) @scroll_position = Vector.new(0, 0)
@scroll_target_position = Vector.new(0, 0)
@scroll_chunk = 120
@scroll_speed = 40 @scroll_speed = 40
@text_color = options[:color] @text_color = options[:color]
@@ -23,37 +36,41 @@ module CyberarmEngine
def build def build
@block.call(self) if @block @block.call(self) if @block
root.gui_state.request_recalculate root.gui_state.request_recalculate_for(self)
end end
def add(element) def add(element)
@children << element @children << element
root.gui_state.request_recalculate root.gui_state.request_recalculate_for(self)
end
def remove(element)
root.gui_state.request_recalculate_for(self) if @children.delete(element)
end end
def clear(&block) def clear(&block)
@children.clear @children.clear
old_container = $__current_container__ old_container = CyberarmEngine::Element::Container.current_container
$__current_container__ = self CyberarmEngine::Element::Container.current_container = self
block.call(self) if block block.call(self) if block
$__current_container__ = old_container CyberarmEngine::Element::Container.current_container = old_container
root.gui_state.request_recalculate root.gui_state.request_recalculate_for(self)
end end
def append(&block) def append(&block)
old_container = $__current_container__ old_container = CyberarmEngine::Element::Container.current_container
$__current_container__ = self CyberarmEngine::Element::Container.current_container = self
block.call(self) if block block.call(self) if block
$__current_container__ = old_container CyberarmEngine::Element::Container.current_container = old_container
root.gui_state.request_recalculate root.gui_state.request_recalculate_for(self)
end end
def render def render
@@ -63,9 +80,11 @@ module CyberarmEngine
content_width + 1, content_width + 1,
content_height + 1 content_height + 1
) do ) do
Gosu.translate(@scroll_position.x, @scroll_position.y) do
@children.each(&:draw) @children.each(&:draw)
end end
end end
end
def debug_draw def debug_draw
super super
@@ -76,35 +95,74 @@ module CyberarmEngine
end end
def update def update
update_scroll
@children.each(&:update) @children.each(&:update)
end end
def hit_element?(x, y) def hit_element?(x, y)
return unless hit?(x, y) return unless hit?(x, y)
# Offset child hit point by scroll position/offset
child_x = x - @scroll_position.x
child_y = y - @scroll_position.y
@children.reverse_each do |child| @children.reverse_each do |child|
next unless child.visible? next unless child.visible?
case child case child
when Container when Container
if element = child.hit_element?(x, y) if element = child.hit_element?(child_x, child_y)
return element return element
end end
else else
return child if child.hit?(x, y) return child if child.hit?(child_x, child_y)
end end
end end
self if hit?(x, y) self if hit?(x, y)
end end
def update_child_element_visibity(child)
child.element_visible = child.x >= (@x - @scroll_position.x) - child.width && child.x <= (@x - @scroll_position.x) + width &&
child.y >= (@y - @scroll_position.y) - child.height && child.y <= (@y - @scroll_position.y) + height
end
def update_scroll
dt = window.dt > 1 ? 1.0 : window.dt
scroll_x_diff = (@scroll_target_position.x - @scroll_position.x)
scroll_y_diff = (@scroll_target_position.y - @scroll_position.y)
@scroll_position.x += (scroll_x_diff * @scroll_speed * 0.25 * dt).round
@scroll_position.y += (scroll_y_diff * @scroll_speed * 0.25 * dt).round
@scroll_position.x = @scroll_target_position.x if scroll_x_diff.abs < 1.0
@scroll_position.y = @scroll_target_position.y if scroll_y_diff.abs < 1.0
# Scrolled PAST top
if @scroll_position.y > 0
@scroll_target_position.y = 0
# Scrolled PAST bottom
elsif @scroll_position.y.abs > max_scroll_height
@scroll_target_position.y = -max_scroll_height
end
if @last_scroll_position != @scroll_position
@children.each { |child| update_child_element_visibity(child) }
root.gui_state.request_repaint
end
@last_scroll_position.x = @scroll_position.x
@last_scroll_position.y = @scroll_position.y
end
def recalculate def recalculate
@current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top) @current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top)
@current_position += @scroll_position
return unless visible? return unless visible?
Stats.increment(:gui_recalculations_last_frame, 1) Stats.frame.increment(:gui_recalculations)
stylize stylize
@@ -112,6 +170,12 @@ module CyberarmEngine
layout layout
old_width = @width
old_height = @height
@cached_scroll_width = nil
@cached_scroll_height = nil
if is_root? if is_root?
@width = @style.width = window.width @width = @style.width = window.width
@height = @style.height = window.height @height = @style.height = window.height
@@ -122,11 +186,35 @@ module CyberarmEngine
_width = dimensional_size(@style.width, :width) _width = dimensional_size(@style.width, :width)
_height = dimensional_size(@style.height, :height) _height = dimensional_size(@style.height, :height)
@width = _width || (@children.map { |c| c.x + c.outer_width }.max || 0).round @width = _width || (@children.map { |c| c.x + c.outer_width }.max || 0).floor
@height = _height || (@children.map { |c| c.y + c.outer_height }.max || 0).round @height = _height || (@children.map { |c| c.y + c.outer_height }.max || 0).floor
end end
# Move child to parent after positioning # FIXME: Correctly handle alignment when element has siblings
# FIXME: Enable alignment for any element, not just containers
if @style.v_align
space = space_available_height
case @style.v_align
when :center
@y = parent.height / 2 - height / 2
when :bottom
@y = parent.height - height
end
end
if @style.h_align
space = space_available_width
case @style.h_align
when :center
@x = parent.width / 2 - width / 2
when :right
@x = parent.width - width
end
end
# Move children to parent after positioning
@children.each do |child| @children.each do |child|
child.x += (@x + @style.border_thickness_left) - style.margin_left child.x += (@x + @style.border_thickness_left) - style.margin_left
child.y += (@y + @style.border_thickness_top) - style.margin_top child.y += (@y + @style.border_thickness_top) - style.margin_top
@@ -135,15 +223,29 @@ module CyberarmEngine
child.recalculate child.recalculate
child.reposition # TODO: Implement top,bottom,left,center, and right positioning child.reposition # TODO: Implement top,bottom,left,center, and right positioning
Stats.increment(:gui_recalculations_last_frame, 1) Stats.frame.increment(:gui_recalculations)
child.element_visible = child.x >= @x - child.width && child.x <= @x + width && update_child_element_visibity(child)
child.y >= @y - child.height && child.y <= @y + height
end end
# puts "TOOK: #{Gosu.milliseconds - s}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}" # puts "TOOK: #{Gosu.milliseconds - s}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}"
update_background update_background
# Fixes resized container scrolled past bottom
self.scroll_top = -@scroll_position.y
@scroll_target_position.y = @scroll_position.y
# NOTE: Experiment for removing need to explicitly call gui_state#recalculate at least 3 times for layout to layout...
if old_width != @width || old_height != @height
if @parent
root.gui_state.request_recalculate_for(@parent)
else
root.gui_state.request_recalculate
end
end
root.gui_state.request_repaint if @width != old_width || @height != old_height
end end
def layout def layout
@@ -151,16 +253,17 @@ module CyberarmEngine
end end
def max_width def max_width
_width = dimensional_size(@style.width, :width) # _width = dimensional_size(@style.width, :width)
if _width # if _width
# outer_width
# else
# window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right)
# end
outer_width outer_width
else
window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right)
end
end end
def fits_on_line?(element) # Flow def fits_on_line?(element) # Flow
p [@options[:id], @width] if @options[:id]
@current_position.x + element.outer_width <= max_width && @current_position.x + element.outer_width <= max_width &&
@current_position.x + element.outer_width <= window.width @current_position.x + element.outer_width <= window.width
end end
@@ -170,7 +273,6 @@ module CyberarmEngine
element.y = element.style.margin_top + @current_position.y element.y = element.style.margin_top + @current_position.y
@current_position.x += element.outer_width @current_position.x += element.outer_width
@current_position.x = @style.margin_left if @current_position.x >= max_width
end end
def tallest_neighbor(querier, _y_position) # Flow def tallest_neighbor(querier, _y_position) # Flow
@@ -183,14 +285,14 @@ module CyberarmEngine
response response
end end
def position_on_next_line(child) # Flow def position_on_next_line(element) # Flow
@current_position.x = @style.margin_left @current_position.x = @style.margin_left + @style.padding_left
@current_position.y += tallest_neighbor(child, @current_position.y).outer_height @current_position.y += tallest_neighbor(element, @current_position.y).outer_height
child.x = child.style.margin_left + @current_position.x element.x = element.style.margin_left + @current_position.x
child.y = child.style.margin_top + @current_position.y element.y = element.style.margin_top + @current_position.y
@current_position.x += child.outer_width @current_position.x += element.outer_width
end end
def move_to_next_line(element) # Stack def move_to_next_line(element) # Stack
@@ -203,11 +305,13 @@ module CyberarmEngine
def mouse_wheel_up(sender, x, y) def mouse_wheel_up(sender, x, y)
return unless @style.scroll return unless @style.scroll
if @scroll_position.y < 0 # Allow overscrolling UP, only if one can scroll DOWN
@scroll_position.y += @scroll_speed if height < scroll_height
@scroll_position.y = 0 if @scroll_position.y > 0 if @scroll_target_position.y > 0
@scroll_target_position.y = @scroll_chunk
root.gui_state.request_recalculate_for(self) else
@scroll_target_position.y += @scroll_chunk
end
return :handled return :handled
end end
@@ -218,15 +322,14 @@ module CyberarmEngine
return unless height < scroll_height return unless height < scroll_height
if @scroll_position.y.abs < max_scroll_height if @scroll_target_position.y > 0
@scroll_position.y -= @scroll_speed @scroll_target_position.y = -@scroll_chunk
@scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height else
@scroll_target_position.y -= @scroll_chunk
root.gui_state.request_recalculate_for(self) end
return :handled return :handled
end end
end
def scroll_top def scroll_top
@scroll_position.y @scroll_position.y
@@ -244,7 +347,7 @@ module CyberarmEngine
end end
def value def value
@children.map { |c| c.class }.join(", ") @children.map(&:class).join(", ")
end end
def to_s def to_s

View File

@@ -98,7 +98,7 @@ module CyberarmEngine
end end
def row_at(y) def row_at(y)
((y - @text.y) / @text.textobject.height).round ((y - @text.y) / @text.textobject.height).floor
end end
def column_at(x, y) def column_at(x, y)

View File

@@ -1,6 +1,20 @@
module CyberarmEngine module CyberarmEngine
class Element class Element
class EditLine < Button class EditLine < Button
class TextInput < Gosu::TextInput
def filter=(filter)
@filter = filter
end
def filter(text_in)
if @filter
@filter.call(text_in)
else
text_in
end
end
end
def initialize(text, options = {}, block = nil) def initialize(text, options = {}, block = nil)
@filter = options.delete(:filter) @filter = options.delete(:filter)
super(text, options, block) super(text, options, block)
@@ -14,17 +28,11 @@ module CyberarmEngine
@caret_last_interval = Gosu.milliseconds @caret_last_interval = Gosu.milliseconds
@show_caret = true @show_caret = true
@text_input = Gosu::TextInput.new @text_input = TextInput.new
@text_input.filter = @filter
@text_input.text = text @text_input.text = text
@last_text_value = text @last_text_value = text
@last_caret_position = @text_input.caret_pos
if @filter && @filter.respond_to?(:call)
@text_input.instance_variable_set(:@filter, @filter)
def @text_input.filter(text_in)
@filter.call(text_in)
end
end
@offset_x = 0 @offset_x = 0
@offset_y = 0 @offset_y = 0
@@ -72,10 +80,22 @@ module CyberarmEngine
@show_caret = true @show_caret = true
@caret_last_interval = Gosu.milliseconds @caret_last_interval = Gosu.milliseconds
root.gui_state.request_repaint
publish(:changed, value) publish(:changed, value)
end end
if @last_caret_position != @text_input.caret_pos
@last_caret_position = @text_input.caret_pos
root.gui_state.request_repaint
@show_caret = true
@caret_last_interval = Gosu.milliseconds
end
if Gosu.milliseconds >= @caret_last_interval + @caret_interval if Gosu.milliseconds >= @caret_last_interval + @caret_interval
root.gui_state.request_repaint
@caret_last_interval = Gosu.milliseconds @caret_last_interval = Gosu.milliseconds
@show_caret = !@show_caret @show_caret = !@show_caret
@@ -98,20 +118,20 @@ module CyberarmEngine
@text_input.caret_pos = @text_input.text.length @text_input.caret_pos = @text_input.text.length
when Gosu::KB_C when Gosu::KB_C
if @text_input.selection_start < @text_input.caret_pos Gosu.clipboard = if @text_input.selection_start < @text_input.caret_pos
Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos]) @text_input.text[@text_input.selection_start...@text_input.caret_pos]
else else
Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start]) @text_input.text[@text_input.caret_pos...@text_input.selection_start]
end end
when Gosu::KB_X when Gosu::KB_X
chars = @text_input.text.chars chars = @text_input.text.chars
if @text_input.selection_start < @text_input.caret_pos if @text_input.selection_start < @text_input.caret_pos
Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos]) Gosu.clipboard = @text_input.text[@text_input.selection_start...@text_input.caret_pos]
chars.slice!(@text_input.selection_start, @text_input.caret_pos) chars.slice!(@text_input.selection_start, @text_input.caret_pos)
else else
Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start]) Gosu.clipboard = @text_input.text[@text_input.caret_pos...@text_input.selection_start]
chars.slice!(@text_input.caret_pos, @text_input.selection_start) chars.slice!(@text_input.caret_pos, @text_input.selection_start)
end end
@@ -119,10 +139,9 @@ module CyberarmEngine
when Gosu::KB_V when Gosu::KB_V
if instance_of?(EditLine) # EditLine assumes a single line of text if instance_of?(EditLine) # EditLine assumes a single line of text
@text_input.text = @text_input.text.insert(@text_input.caret_pos, @text_input.insert_text(Gosu.clipboard.gsub("\n", ""))
Clipboard.paste.encode("UTF-8").gsub("\n", ""))
else else
@text_input.text = @text_input.text.insert(@text_input.caret_pos, Clipboard.paste.encode("UTF-8")) @text_input.insert_text(Gosu.clipboard)
end end
end end
end end
@@ -180,7 +199,7 @@ module CyberarmEngine
if @type == :password if @type == :password
@text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length) @text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length)
else else
@text.x + @text.width(@text_input.text[0...@text_input.send(method)]) @text.x + @text.width(@text_input.text[0...@text_input.send(method)]) - @style.border_thickness_left
end end
end end
@@ -197,20 +216,35 @@ module CyberarmEngine
end end
def focus(sender) def focus(sender)
super @focus = true
window.text_input = @text_input window.text_input = @text_input
@text_input.caret_pos = @text_input.selection_start = @text_input.text.length @text_input.caret_pos = @text_input.selection_start = @text_input.text.length
update_styles(:active)
:handled :handled
end end
def enter(sender) def enter(sender)
_has_focus = @focus if @enabled && @focus
update_styles(:active)
elsif @enabled && !@focus
update_styles(:hover)
else
update_styles(:disabled)
end
super :handled
end
@focus = _has_focus def leave(sender)
if @enabled && @focus
update_styles(:active)
elsif @enabled && !@focus
update_styles
else
update_styles(:disabled)
end
:handled :handled
end end

View File

@@ -45,8 +45,8 @@ module CyberarmEngine
@scale_y = 1 @scale_y = 1
end end
@width = _width || @image.width.round * @scale_x @width = _width || (@image.width * @scale_x).floor
@height = _height || @image.height.round * @scale_y @height = _height || (@image.height * @scale_y).floor
update_background update_background
end end

View File

@@ -13,10 +13,12 @@ module CyberarmEngine
@style.background_canvas.background = default(:background) @style.background_canvas.background = default(:background)
# TODO: "Clean Up" into own class? # TODO: "Clean Up" into own class?
@menu = Stack.new(parent: parent, width: @options[:width], theme: @options[:theme]) @menu = Stack.new(parent: self, theme: @options[:theme])
@menu.define_singleton_method(:recalculate_menu) do @menu.define_singleton_method(:recalculate_menu) do
@x = @__list_box.x @x = @__list_box.x
@y = @__list_box.y + @__list_box.height @y = @__list_box.y + @__list_box.height
@y = @__list_box.y - height if @y + height > window.height
end end
@menu.instance_variable_set(:"@__list_box", self) @menu.instance_variable_set(:"@__list_box", self)
@@ -29,6 +31,13 @@ module CyberarmEngine
self.choose = @choose self.choose = @choose
end end
def render
super
w = @text.textobject.text_width("")
@text.textobject.draw_text("", @x + content_width - w, @y + @style.padding_top, @z, 1, 1, @text.color)
end
def choose=(item) def choose=(item)
valid = @items.detect { |i| i == item } valid = @items.detect { |i| i == item }
raise "Invalid value '#{item}' for choose, valid options were: #{@items.map { |i| "#{i.inspect}" }.join(", ")}" unless valid raise "Invalid value '#{item}' for choose, valid options were: #{@items.map { |i| "#{i.inspect}" }.join(", ")}" unless valid
@@ -55,6 +64,8 @@ module CyberarmEngine
def show_menu def show_menu
@menu.clear @menu.clear
@menu.style.width = width
@items.each do |item| @items.each do |item|
next if item == self.value next if item == self.value

View File

@@ -52,6 +52,7 @@ module CyberarmEngine
@marquee_animation_time = Gosu.milliseconds if @marquee_offset > range @marquee_animation_time = Gosu.milliseconds if @marquee_offset > range
update_background update_background
root.gui_state.request_repaint
end end
def type=(type) def type=(type)
@@ -77,9 +78,13 @@ module CyberarmEngine
def value=(decimal) def value=(decimal)
raise "value must be number" unless decimal.is_a?(Numeric) raise "value must be number" unless decimal.is_a?(Numeric)
old_value = @fraction
@fraction = decimal.clamp(0.0, 1.0) @fraction = decimal.clamp(0.0, 1.0)
update_background update_background
root.gui_state.request_repaint if @fraction != old_value
publish(:changed, @fraction) publish(:changed, @fraction)
@fraction @fraction
end end

View File

@@ -33,7 +33,8 @@ module CyberarmEngine
end end
end end
attr_reader :range, :step_size, :value attr_reader :step_size, :value
attr_accessor :range, :step_size
def initialize(options = {}, block = nil) def initialize(options = {}, block = nil)
super(options, block) super(options, block)
@@ -42,7 +43,7 @@ module CyberarmEngine
@step_size = @options[:step] || 0.1 @step_size = @options[:step] || 0.1
@value = @options[:value] || (@range.first + @range.last) / 2 @value = @options[:value] || (@range.first + @range.last) / 2
@handle = Handle.new("", parent: self, width: 8, height: 1.0) { close } @handle = Handle.new("", parent: self, theme: options[:theme], width: 8, height: 1.0) { close }
add(@handle) add(@handle)
end end
@@ -61,10 +62,10 @@ module CyberarmEngine
end end
def position_handle def position_handle
@handle.x = @x + @style.padding_left + @style.border_thickness_left + @handle.x = @x + @handle.style.margin_left + @style.padding_left + @style.border_thickness_left +
((content_width - @handle.outer_width) * (@value - @range.min) / (@range.max - @range.min).to_f) ((content_width - @handle.outer_width) * (@value - @range.min) / (@range.max - @range.min).to_f)
@handle.y = @y + @style.border_thickness_top + @style.padding_top @handle.y = @y + @handle.style.margin_top + @style.border_thickness_top + @style.padding_top
end end
def draw def draw
@@ -76,7 +77,7 @@ module CyberarmEngine
def update def update
super super
@tip = value.to_s @tip = format("%.2f", value.to_f)
@handle.tip = @tip @handle.tip = @tip
end end
@@ -87,7 +88,7 @@ module CyberarmEngine
end end
def handle_dragged_to(x, _y) def handle_dragged_to(x, _y)
@ratio = ((x - @handle.width / 2) - @x) / (content_width - @handle.outer_width) @ratio = ((x - @handle.width / 2.0) - @x) / (content_width - @handle.outer_width.to_f)
self.value = @ratio.clamp(0.0, 1.0) * (@range.max - @range.min) + @range.min self.value = @ratio.clamp(0.0, 1.0) * (@range.max - @range.min) + @range.min
end end
@@ -97,6 +98,8 @@ module CyberarmEngine
position_handle position_handle
@handle.recalculate @handle.recalculate
root.gui_state.request_repaint
publish(:changed, @value) publish(:changed, @value)
end end
end end

View File

@@ -7,6 +7,7 @@ module CyberarmEngine
@text = Text.new( @text = Text.new(
text, font: @options[:font], z: @z, color: @options[:color], text, font: @options[:font], z: @z, color: @options[:color],
size: @options[:text_size], shadow: @options[:text_shadow], size: @options[:text_size], shadow: @options[:text_shadow],
static: @options[:text_static],
shadow_size: @options[:text_shadow_size], shadow_size: @options[:text_shadow_size],
shadow_color: @options[:text_shadow_color], shadow_color: @options[:text_shadow_color],
border: @options[:text_border], border: @options[:text_border],
@@ -17,9 +18,25 @@ module CyberarmEngine
@raw_text = text @raw_text = text
end end
def update
super
if @text.textobject.name != safe_style_fetch(:font)
set_font
root.gui_state.request_recalculate
end
end
def render def render
# Gosu.clip_to is too expensive to always use so check if we actually need it.
if @text.width > width || @text.height > height
Gosu.clip_to(@x, @y, width, height) do
@text.draw @text.draw
end end
else
@text.draw
end
end
def recalculate def recalculate
unless @enabled unless @enabled
@@ -28,6 +45,9 @@ module CyberarmEngine
@text.color = @style.color @text.color = @style.color
end end
old_width = @width
old_height = @height
@width = 0 @width = 0
@height = 0 @height = 0
@@ -36,13 +56,13 @@ module CyberarmEngine
handle_text_wrapping(_width) handle_text_wrapping(_width)
@width = _width || @text.width.round @width = _width || @text.width.floor
@height = _height || @text.height.round @height = _height || @text.height.floor
@text.y = @style.border_thickness_top + @style.padding_top + @y @text.y = @style.border_thickness_top + @style.padding_top + @y
@text.z = @z + 3 @text.z = @z + 3
if (text_alignment = @options[:text_align]) if (text_alignment = @options[:text_align] || @options[:text_h_align])
case text_alignment case text_alignment
when :left when :left
@text.x = @style.border_thickness_left + @style.padding_left + @x @text.x = @style.border_thickness_left + @style.padding_left + @x
@@ -57,7 +77,22 @@ module CyberarmEngine
end end
end end
if (vertical_alignment = @options[:text_v_align])
case vertical_alignment
when :center
@text.y = if @text.height <= height
@y + height / 2 - @text.height / 2
else
@style.border_thickness_top + @style.padding_top + @y
end
when :bottom
@text.y = @y + outer_height - (@text.height + @style.border_thickness_bottom + @style.padding_bottom)
end
end
update_background update_background
root.gui_state.request_repaint if @width != old_width || @height != old_height
end end
def handle_text_wrapping(max_width) def handle_text_wrapping(max_width)
@@ -130,7 +165,7 @@ module CyberarmEngine
end end
def line_width(text) def line_width(text)
(@text.textobject.markup_width(text) + noncontent_width) (@text.textobject.markup_width(text.to_s) + noncontent_width)
end end
def value def value
@@ -138,6 +173,7 @@ module CyberarmEngine
end end
def value=(value) def value=(value)
old_value = @raw_text
@raw_text = value.to_s.chomp @raw_text = value.to_s.chomp
old_width = width old_width = width
@@ -149,6 +185,8 @@ module CyberarmEngine
recalculate recalculate
end end
root.gui_state.request_repaint if old_value != @raw_text
publish(:changed, self.value) publish(:changed, self.value)
end end
end end

View File

@@ -5,7 +5,7 @@ module CyberarmEngine
def initialize(options, block = nil) def initialize(options, block = nil)
if options.dig(:theme, :ToggleButton, :checkmark_image) if options.dig(:theme, :ToggleButton, :checkmark_image)
options[:theme][:ToggleButton][:image_width] ||= options[:theme][:Label][:text_size] options[:theme][:ToggleButton][:image_width] ||= options[:theme][:TextBlock][:text_size]
super(get_image(options.dig(:theme, :ToggleButton, :checkmark_image)), options, block) super(get_image(options.dig(:theme, :ToggleButton, :checkmark_image)), options, block)
@_image = @image @_image = @image

View File

@@ -12,7 +12,7 @@ module CyberarmEngine
@root_container = Element::Stack.new(gui_state: self) @root_container = Element::Stack.new(gui_state: self)
@game_objects << @root_container @game_objects << @root_container
$__current_container__ = @root_container CyberarmEngine::Element::Container.current_container = @root_container
@active_width = window.width @active_width = window.width
@active_height = window.height @active_height = window.height
@@ -27,6 +27,8 @@ module CyberarmEngine
@pending_recalculate_request = false @pending_recalculate_request = false
@pending_element_recalculate_requests = [] @pending_element_recalculate_requests = []
@needs_repaint = false
@menu = nil @menu = nil
@min_drag_distance = 0 @min_drag_distance = 0
@mouse_pos = Vector.new @mouse_pos = Vector.new
@@ -55,31 +57,44 @@ module CyberarmEngine
@menu.draw @menu.draw
end end
if @tip.value.length.positive? if @tip && @tip.value.length.positive?
Gosu.flush Gosu.flush
@tip.draw @tip.draw
end end
if defined?(GUI_DEBUG) # FIXME
if false# defined?(GUI_DEBUG)
Gosu.flush Gosu.flush
@root_container.debug_draw @root_container.debug_draw
end end
@needs_repaint = false
end
def needs_repaint?
@needs_repaint
end end
def update def update
if @pending_recalculate_request if @pending_recalculate_request
@root_container.recalculate Stats.frame.start_timing(:gui_recalculate)
@root_container.recalculate
@root_container.recalculate @root_container.recalculate
@pending_recalculate_request = false @pending_recalculate_request = false
Stats.frame.end_timing(:gui_recalculate)
end end
Stats.frame.start_timing(:gui_element_recalculate_requests)
# puts "PENDING REQUESTS: #{@pending_element_recalculate_requests.size}" if @pending_element_recalculate_requests.size.positive?
@pending_element_recalculate_requests.each(&:recalculate) @pending_element_recalculate_requests.each(&:recalculate)
@pending_element_recalculate_requests.clear @pending_element_recalculate_requests.clear
Stats.frame.end_timing(:gui_element_recalculate_requests)
if @pending_focus_request if @pending_focus_request
@pending_focus_request = false @pending_focus_request = false
@@ -88,8 +103,20 @@ module CyberarmEngine
end end
@menu&.update @menu&.update
super super
if @active_width != window.width || @active_height != window.height
request_recalculate
@root_container.publish(:window_size_changed)
end
@active_width = window.width
@active_height = window.height
return unless window.has_focus?
return unless window.current_state == self
new_mouse_over = @menu.hit_element?(window.mouse_x, window.mouse_y) if @menu new_mouse_over = @menu.hit_element?(window.mouse_x, window.mouse_y) if @menu
new_mouse_over ||= @root_container.hit_element?(window.mouse_x, window.mouse_y) new_mouse_over ||= @root_container.hit_element?(window.mouse_x, window.mouse_y)
@@ -101,9 +128,9 @@ module CyberarmEngine
@mouse_over.publish(:leave) if @mouse_over && new_mouse_over != @mouse_over @mouse_over.publish(:leave) if @mouse_over && new_mouse_over != @mouse_over
@mouse_over = new_mouse_over @mouse_over = new_mouse_over
redirect_holding_mouse_button(:left) if @mouse_over && Gosu.button_down?(Gosu::MsLeft) redirect_holding_mouse_button(:left) if @mouse_over && Gosu.button_down?(Gosu::MS_LEFT)
redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MsMiddle) redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MS_MIDDLE)
redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MsRight) redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MS_RIGHT)
if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos
if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay
@@ -125,31 +152,19 @@ module CyberarmEngine
@last_mouse_pos = Vector.new(window.mouse_x, window.mouse_y) @last_mouse_pos = Vector.new(window.mouse_x, window.mouse_y)
@mouse_pos = @last_mouse_pos.clone @mouse_pos = @last_mouse_pos.clone
if @active_width != window.width || @active_height != window.height
request_recalculate
@root_container.publish(:window_size_changed)
end
@active_width = window.width
@active_height = window.height
end
def tool_tip_delay
250 # ms
end end
def button_down(id) def button_down(id)
super super
case id case id
when Gosu::MsLeft when Gosu::MS_LEFT
redirect_mouse_button(:left) redirect_mouse_button(:left)
when Gosu::MsMiddle when Gosu::MS_MIDDLE
redirect_mouse_button(:middle) redirect_mouse_button(:middle)
when Gosu::MsRight when Gosu::MS_RIGHT
redirect_mouse_button(:right) redirect_mouse_button(:right)
when Gosu::KbF5 when Gosu::KB_F5
request_recalculate request_recalculate
end end
@@ -160,19 +175,26 @@ module CyberarmEngine
super super
case id case id
when Gosu::MsLeft when Gosu::MS_LEFT
redirect_released_mouse_button(:left) redirect_released_mouse_button(:left)
when Gosu::MsMiddle when Gosu::MS_MIDDLE
redirect_released_mouse_button(:middle) redirect_released_mouse_button(:middle)
when Gosu::MsRight when Gosu::MS_RIGHT
redirect_released_mouse_button(:right) redirect_released_mouse_button(:right)
when Gosu::MsWheelUp when Gosu::MS_WHEEL_UP
redirect_mouse_wheel(:up) redirect_mouse_wheel(:up)
when Gosu::MsWheelDown when Gosu::MS_WHEEL_DOWN
redirect_mouse_wheel(:down) redirect_mouse_wheel(:down)
end end
@focus.button_up(id) if @focus.respond_to?(:button_up) @focus.button_up(id) if @focus.respond_to?(:button_up)
# Prevents menu from popping back up if the listbox is clicked to hide it.
@hid_menu_for = nil
end
def tool_tip_delay
@tip.style.delay || 250 # ms
end end
def redirect_mouse_button(button) def redirect_mouse_button(button)
@@ -183,7 +205,7 @@ module CyberarmEngine
@focus = nil @focus = nil
end end
if @mouse_over if @mouse_over && @hid_menu_for != @mouse_over
@mouse_down_position[button] = Vector.new(window.mouse_x, window.mouse_y) @mouse_down_position[button] = Vector.new(window.mouse_x, window.mouse_y)
@mouse_down_on[button] = @mouse_over @mouse_down_on[button] = @mouse_over
@@ -197,7 +219,7 @@ module CyberarmEngine
def redirect_released_mouse_button(button) def redirect_released_mouse_button(button)
hide_menu if @menu && (@menu == @mouse_over) || (@mouse_over&.parent == @menu) hide_menu if @menu && (@menu == @mouse_over) || (@mouse_over&.parent == @menu)
if @mouse_over if @mouse_over && @hid_menu_for != @mouse_over
@mouse_over.publish(:"released_#{button}_mouse_button", window.mouse_x, window.mouse_y) @mouse_over.publish(:"released_#{button}_mouse_button", window.mouse_x, window.mouse_y)
if @mouse_over == @mouse_down_on[button] if @mouse_over == @mouse_down_on[button]
@mouse_over.publish(:"clicked_#{button}_mouse_button", window.mouse_x, @mouse_over.publish(:"clicked_#{button}_mouse_button", window.mouse_x,
@@ -248,11 +270,22 @@ module CyberarmEngine
@pending_focus_element = element @pending_focus_element = element
end end
def request_repaint
# puts caller[0..4]
# puts
@needs_repaint = true
end
def show_menu(list_box) def show_menu(list_box)
@menu = list_box @menu = list_box
end end
def hide_menu def hide_menu
return unless @menu
request_repaint
@hid_menu_for = @menu.parent
@menu = nil @menu = nil
end end

View File

@@ -20,7 +20,8 @@ module CyberarmEngine
attr_reader :hash attr_reader :hash
def initialize(hash = {}) def initialize(hash = {})
h = Marshal.load(Marshal.dump(hash)) h = hash
# h = Marshal.load(Marshal.dump(hash))
h[:default] = {} h[:default] = {}

View File

@@ -76,6 +76,7 @@ module CyberarmEngine
border_radius: 0, border_radius: 0,
background: ["ffc75e61".to_i(16), "ffe26623".to_i(16)], background: ["ffc75e61".to_i(16), "ffe26623".to_i(16)],
text_align: :center, text_align: :center,
text_v_align: :center,
text_wrap: :none, text_wrap: :none,
hover: { hover: {
@@ -102,7 +103,12 @@ module CyberarmEngine
caret_color: Gosu::Color::WHITE, caret_color: Gosu::Color::WHITE,
caret_interval: 500, caret_interval: 500,
selection_color: Gosu::Color.rgba(255, 128, 50, 200), selection_color: Gosu::Color.rgba(255, 128, 50, 200),
text_align: :left text_align: :left,
text_static: false # static text causes issues correctly displaying caret position
},
EditBox: { # < EditLine
text_v_align: :top
}, },
Image: { # < Element Image: { # < Element
@@ -154,6 +160,7 @@ module CyberarmEngine
}, },
ToolTip: { # < TextBlock ToolTip: { # < TextBlock
delay: 100, # ms
color: Gosu::Color::WHITE, color: Gosu::Color::WHITE,
padding_top: 4, padding_top: 4,
padding_bottom: 4, padding_bottom: 4,

View File

@@ -95,39 +95,58 @@ module CyberarmEngine
Vector.new(@x, @y) Vector.new(@x, @y)
end end
# Performs math operation, excluding {weight}
private def operator(function, other)
if other.is_a?(Numeric)
Vector.new(
@x.send(:"#{function}", other),
@y.send(:"#{function}", other),
@z.send(:"#{function}", other)
)
else
Vector.new(
@x.send(:"#{function}", other.x),
@y.send(:"#{function}", other.y),
@z.send(:"#{function}", other.z)
)
end
end
# Adds Vector and Numeric or Vector and Vector, excluding {weight} # Adds Vector and Numeric or Vector and Vector, excluding {weight}
# @return [CyberarmEngine::Vector] # @return [CyberarmEngine::Vector]
def +(other) def +(other)
operator("+", other) if other.is_a?(Numeric)
Vector.new(
@x + other,
@y + other,
@z + other
)
else
Vector.new(
@x + other.x,
@y + other.y,
@z + other.z
)
end
end end
# Subtracts Vector and Numeric or Vector and Vector, excluding {weight} # Subtracts Vector and Numeric or Vector and Vector, excluding {weight}
# @return [CyberarmEngine::Vector] # @return [CyberarmEngine::Vector]
def -(other) def -(other)
operator("-", other) if other.is_a?(Numeric)
Vector.new(
@x - other,
@y - other,
@z - other
)
else
Vector.new(
@x - other.x,
@y - other.y,
@z - other.z
)
end
end end
# Multiplies Vector and Numeric or Vector and Vector, excluding {weight} # Multiplies Vector and Numeric or Vector and Vector, excluding {weight}
# @return [CyberarmEngine::Vector] # @return [CyberarmEngine::Vector]
def *(other) def *(other)
operator("*", other) if other.is_a?(Numeric)
Vector.new(
@x * other,
@y * other,
@z * other
)
else
Vector.new(
@x * other.x,
@y * other.y,
@z * other.z
)
end
end end
def multiply_transform(transform) def multiply_transform(transform)

View File

@@ -1,4 +1,4 @@
module CyberarmEngine module CyberarmEngine
NAME = "InDev".freeze NAME = "InDev".freeze
VERSION = "0.20.0".freeze VERSION = "0.23.0".freeze
end end

View File

@@ -6,49 +6,130 @@ module CyberarmEngine
SAMPLES = {} SAMPLES = {}
SONGS = {} SONGS = {}
attr_accessor :show_cursor attr_accessor :show_cursor, :show_stats_plotter
attr_writer :exit_on_opengl_error attr_writer :exit_on_opengl_error
attr_reader :last_frame_time attr_reader :last_frame_time, :delta_time, :states
def self.now def self.now
Gosu.milliseconds Gosu.milliseconds
end end
def self.dt def self.dt
$window.last_frame_time / 1000.0 instance.dt
end
def self.instance=(window)
raise ArgumentError, "Expected window to be a subclass of CyberarmEngine::Window, got: #{window.class}" unless window.is_a?(CyberarmEngine::Window)
@@instance = window
end
def self.instance
@@instance
end end
def initialize(width: 800, height: 600, fullscreen: false, update_interval: 1000.0 / 60, resizable: false, borderless: false) def initialize(width: 800, height: 600, fullscreen: false, update_interval: 1000.0 / 60, resizable: false, borderless: false)
@show_cursor = false @show_cursor = false
@has_focus = false
@show_stats_plotter = false
super(width, height, fullscreen: fullscreen, update_interval: update_interval, resizable: resizable, borderless: borderless) super(width, height, fullscreen: fullscreen, update_interval: update_interval, resizable: resizable, borderless: borderless)
$window = self Window.instance = self
@last_frame_time = Gosu.milliseconds - 1 @last_frame_time = Gosu.milliseconds - 1
@current_frame_time = Gosu.milliseconds @current_frame_time = Gosu.milliseconds
self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.language}" @delta_time = @last_frame_time
self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.user_languages.join(', ')}"
@states = [] @states = []
@exit_on_opengl_error = false @exit_on_opengl_error = false
preload_default_shaders if respond_to?(:preload_default_shaders)
@stats_plotter = Stats::StatsPlotter.new(2, 28) # FIXME: Make positioning easy
setup if defined?(setup) setup
end
def setup
end end
def draw def draw
current_state.draw if current_state Stats.frame.start_timing(:draw)
current_state&.draw
Stats.frame.start_timing(:engine_stats_renderer)
@stats_plotter&.draw if @show_stats_plotter
Stats.frame.end_timing(:engine_stats_renderer)
Stats.frame.end_timing(:draw)
Stats.frame.start_timing(:interframe_sleep)
end end
def update def update
Stats.clear # Gosu calls update() then (optionally) draw(),
# so always end last frame and start next frame when update() is called.
Stats.frame&.end_timing(:interframe_sleep)
Stats.end_frame
Stats.new_frame
@delta_time = (Gosu.milliseconds - @current_frame_time) * 0.001
current_state.update if current_state
@last_frame_time = Gosu.milliseconds - @current_frame_time @last_frame_time = Gosu.milliseconds - @current_frame_time
@current_frame_time = Gosu.milliseconds @current_frame_time = Gosu.milliseconds
Stats.frame.start_timing(:update)
current_state&.update
Stats.frame.end_timing(:update)
Stats.frame.start_timing(:interframe_sleep) unless needs_redraw?
end end
def needs_cursor? def needs_cursor?
@show_cursor @show_cursor
end end
def needs_redraw?
current_state ? current_state.needs_redraw? : true
end
def drop(filename)
current_state&.drop(filename)
end
def gamepad_connected(index)
current_state&.gamepad_connected(index)
end
def gamepad_disconnected(index)
current_state&.gamepad_disconnected(index)
end
def gain_focus
@has_focus = true
current_state&.gain_focus
end
def lose_focus
@has_focus = false
current_state&.lose_focus
end
def button_down(id)
super
current_state&.button_down(id)
end
def button_up(id)
super
current_state&.button_up(id)
end
def close
current_state ? current_state.close : super
end
def dt def dt
@last_frame_time / 1000.0 @last_frame_time / 1000.0
end end
@@ -61,20 +142,10 @@ module CyberarmEngine
@exit_on_opengl_error @exit_on_opengl_error
end end
def button_down(id)
super
current_state.button_down(id) if current_state
end
def button_up(id)
super
current_state.button_up(id) if current_state
end
def push_state(klass, options = {}) def push_state(klass, options = {})
options = { setup: true }.merge(options) options = { setup: true }.merge(options)
if klass.instance_of?(klass.class) && defined?(klass.options) if klass.instance_of?(klass.class) && klass.respond_to?(:options)
@states << klass @states << klass
klass.setup if options[:setup] klass.setup if options[:setup]
klass.post_setup if options[:setup] klass.post_setup if options[:setup]
@@ -87,6 +158,8 @@ module CyberarmEngine
end end
private def child_of?(input, klass) private def child_of?(input, klass)
return false unless input
input.ancestors.detect { |c| c == klass } input.ancestors.detect { |c| c == klass }
end end
@@ -94,18 +167,20 @@ module CyberarmEngine
@states.last @states.last
end end
def previous_state
if @states.size > 1 && (state = @states[@states.size - 2])
state
end
end
def pop_state def pop_state
@states.pop @states.pop
current_state.request_repaint if current_state&.is_a?(GuiState)
end end
def shift_state def shift_state
@states.shift @states.shift
current_state.request_repaint if current_state&.is_a?(GuiState)
end
def has_focus?
@has_focus
end end
# Sourced from https://gist.github.com/ippa/662583 # Sourced from https://gist.github.com/ippa/662583

29
mrbgem.rake Normal file
View File

@@ -0,0 +1,29 @@
MRuby::Gem::Specification.new("mruby-cyberarm_engine") do |spec|
spec.license = "MIT"
spec.authors = "cyberarm"
spec.summary = " Yet another framework for building games with Gosu"
lib_rbfiles = []
# Dir.glob("#{File.expand_path("..", __FILE__)}/lib/**/*.rb").reject do |f|
# File.basename(f.downcase, ".rb") == "cyberarm_engine" ||
# File.basename(f.downcase, ".rb") == "opengl" ||
# f.downcase.include?("/opengl/")
# end.reverse!
local_path = File.expand_path("..", __FILE__)
File.read("#{local_path}/lib/cyberarm_engine.rb").each_line do |line|
line = line.strip
next unless line.start_with?("require_relative")
file = line.split("require_relative").last.strip.gsub("\"", "")
next if file.include?(" if ")
lib_rbfiles << "#{local_path}/lib/#{file}.rb"
end
pp lib_rbfiles
spec.rbfiles = lib_rbfiles
end