92 Commits

Author SHA1 Message Date
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
24bd769a32 Bump version 2022-03-05 14:43:50 -06:00
2be5733bc1 Fixed Container not considering padding when clipping render area 2022-01-29 10:08:33 -06:00
c8734ae98b Container child elements that are not visible are no longer drawn 2021-12-24 17:00:02 -06:00
c35d587419 Fixed passing nil for :enabled state as boolean causing it to be set as true instead of false 2021-12-22 14:58:17 -06:00
153871e7f3 Improved performance a touch: fixed never clearing list of elements to recalculate unless a full gui recalcuate occurred, don't recalculate button/text if a full gui recalculate is requested, fixed typo: apend -> append 2021-12-16 22:09:56 -06:00
cf91d4a083 Merge branch 'master' of https://github.com/cyberarm/cyberarm_engine 2021-12-03 15:33:38 -06:00
35ad687d4c Reducce cpu usage by not using clip_to for each and every element 2021-12-03 15:33:32 -06:00
54802e1254 Added support 'marquee' style progress bars i.e. non-linear progress bar with scrolling bar 2021-12-02 08:15:39 -06:00
6cf4cd73dd Fixed toggling Element enabled state not visually shown 2021-11-18 15:37:57 -06:00
5e5f8ba7ea Added set_color and set_font to Element- fixes image elements unable to change their color when hovered, fixes hovered text changing its color incorrectly 2021-11-18 13:11:02 -06:00
63a51d9d2f Improved scroll_height calculation to adapt to how Flow's flow 2021-11-18 11:58:11 -06:00
6af384536a Fixed locking up when performing text wrapping due to text width check not being satisfied 2021-11-17 22:30:30 -06:00
c1b25d2045 Fixed centered text with unequal margins/paddings/border thicknesses being offset, fixed text wrapping using parents #width (which includes padding and margins) instead of the correct #content_width (which is only the parents content width; which the text is part of) 2021-11-17 17:23:48 -06:00
a915a25699 Updated required gems, reimplemented text wrapping to use a proper binary search and correct inserting newlines in the wrong spot for certain text lengths 2021-11-16 23:35:49 -06:00
0aa9b59316 Containers that need to be recalculated but will not affect their current size can request to have themselves directly recalculated on the next update (fixes scrolling triggering many unneed recalculations), list_box now excludes the currently selected item from the menu, probably fixed list_box callback being called twice instead of the correct once. 2021-11-15 10:17:49 -06:00
d2bf406d29 Bump version 2021-09-23 14:36:19 -05:00
f82c101728 Fixed ListBox sometimes returning self in callback instead of self.value 2021-09-23 14:35:46 -05:00
2f727e9bf2 Bump version 2021-09-22 09:23:24 -05:00
24be9bfb29 Imported Console from I-MIC FPS 2021-06-26 13:12:44 +00:00
5452d149c3 Possible fix for being able to click a button that isn't visible due to scrolling, by ensuring that the host container is hit before checking children. 2021-06-18 02:57:49 +00:00
cdee6548e3 Added support for 9 sliced backgrounds in UI, fixed events not propagating to subscribers if element threw :handled in its own event handler 2021-06-04 03:00:31 +00:00
bd54fafc3f Added Text#text_width method, Text#width now returns value of Text#markup_width 2021-06-03 13:05:11 +00:00
a92d1ad746 Made Text account for shadow and border effects in width/height methods, made TextBlock support disabled state styling 2021-06-03 01:00:13 +00:00
1b080f9fb9 Use shorthand (&:method) for Element scroll_width/height max_scroll_width, possibly fix dimentional_size returning the wrong size when element has nonzero padding/margin/border_thickness 2021-05-31 01:07:24 +00:00
8057bca818 Added rendered vertices count to OpenGLRenderer 2021-05-30 14:10:32 +00:00
c1310f3073 Renamed Text's shadow to border and added proper text shadow effect 2021-05-29 00:10:44 +00:00
850bb610bb Fixed GUI having a total recalculation whenever a style changed, made tooltip not be centered on mouse, removed duplicate loc from Text 2021-05-28 00:43:40 +00:00
0b63986b64 Improved 9 slice background 2021-05-06 03:20:49 +00:00
165fc6f307 Fixed draw_rect helper missing drawing mode argument, set max view distance to a large number 2021-04-25 23:22:58 +00:00
e3c8e3bcc2 Added a bunch of tweens to the Animator, replaced :ease_in_out tween for Intro state engine logo to :swing_to 2021-04-25 14:58:12 +00:00
551a55f894 Add fade out to intro scene 2021-04-24 20:13:06 +00:00
01292ead10 Bump version 2021-04-19 19:26:42 +00:00
7ca6e1bc58 Update README 2021-04-19 19:22:43 +00:00
a70f260bc6 Update README 2021-04-19 19:17:10 +00:00
8002708695 Updated Animator to be usable, added logo texture/image, 'finished' Intro state, added Window#shift_state 2021-04-19 19:13:15 +00:00
676545f3c7 Added wip engine intro state 2021-04-19 14:42:49 +00:00
4b417f9ab7 More changes to how styles are applied, fixed bug where a Container would re-evaluate its build block when clicked, fixed background method in DSL having no effect, fixed edit line losing 'focus' when mouse moved out, misc. other changes and fixes. 2021-03-29 08:51:26 -05:00
a70c106387 Added Link element which is basically a button without a border or background, WIP: Element border, margin, padding, and other styles are now easily changable; work is needed to make style changes survive a recalculation 2021-03-29 08:51:26 -05:00
f662fabc56 Use GL_SRGB_ALPHA for textures 2021-03-29 08:13:59 -05:00
abb989f842 Added flags to use ffi-gosu optionally instead of always trying, made tooltip affected by theme (the last set theme in setup will be used to style the tooltip) 2021-03-12 08:33:16 -06:00
1e0d2004b5 More adjustments to scrolling and scroll_top, added debug_draw to elements that draws a box around the element that is atop all rendered things 2021-02-13 22:57:18 -06:00
92dd63dc1d Probably fixed scrolling for real this time, added scroll_top and scroll_top = n methods 2021-02-13 20:03:10 -06:00
20970e5aa9 Bump version 2021-02-11 09:32:37 -06:00
76eb1a85d5 Added focus event, elements can request focus 2021-02-11 09:31:44 -06:00
e9d75d17bf Updated gosu version, bumped version 2021-02-10 12:19:47 -06:00
e8bb2cac17 Update bundler in gemspec 2021-02-10 12:18:28 -06:00
29fbac7140 Added Container#apend method, EditLine caret will now stay visible while typing, EditLine#value= now sets the value of @text_input 2021-02-09 17:41:29 -06:00
d050f63c2b Increased scroll speed and added window_size_changed event for containers that is thrown from the root container 2021-01-31 13:33:20 -06:00
af24fc8690 Added vertical scrolling support for containers (no scrollbar yet) 2021-01-31 09:44:46 -06:00
f63b893c70 Bump version 2021-01-18 14:01:29 -06:00
5adc27feef Sync 2021-01-18 14:00:29 -06:00
62636158f7 Fixed word wrapping weirdness and made it the default text_wrap method from :none 2021-01-16 19:43:49 -06:00
2179e11ba1 Updated list box 2021-01-07 10:12:14 -06:00
1ac5e0695e Fixed Image element background and border not working, made button use Style as sole source of background and color colors 2021-01-05 23:15:22 -06:00
732dc2c957 Fixed new label dsl methods requiring options to be provided, upgraded Image to accept either a path or an image and to enable replacing image using #value= 2021-01-05 20:39:13 -06:00
886680ab31 Renamed Label to TextBlock, added Shoes text dsl methods; needs mroe refinements, made tooltip styleable. 2021-01-05 17:31:31 -06:00
0268a8a5fb Button element (and its decendents) can now be disabled 2021-01-04 09:34:11 -06:00
1c22c36d6b Include Common in Window 2021-01-01 19:20:52 -06:00
49 changed files with 2704 additions and 588 deletions

View File

@@ -1,4 +1,4 @@
# CyberarmEngine
![CyberarmEngine](https://raw.githubusercontent.com/cyberarm/cyberarm_engine/master/assets/textures/logo.png)
Yet Another Game Engine On Top Of Gosu
@@ -32,6 +32,8 @@ require "cyberarm_engine"
class Hello < CyberarmEngine::GuiState
def setup
background Gosu::Color::GRAY
stack do
label "Hello World!"
@@ -43,15 +45,14 @@ class Hello < CyberarmEngine::GuiState
end
class Window < CyberarmEngine::Window
def initialize
super
def setup
self.show_cursor = true
push_state(Hello)
end
end
Window.new.show
Window.new(width: 800, height: 600, fullscreen: false, resizable: true).show
```
## Development

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 outPosition, outColor, outNormal, outUV, outFragPos, outCameraPos;
out vec4 outputFragColor;
flat in int outHasTexture;
uniform sampler2D diffuse_texture;
void main() {
vec3 result;
if (outHasTexture == 0) {
result = outColor;
} else {
result = texture(diffuse_texture, outUV.xy).xyz + 0.25;
}
fragPosition = outPosition;
fragColor = vec4(result, 1.0);
fragNormal = outNormal;
fragUV = outUV;
float gamma = 2.2;
outputFragColor.rgb = pow(fragColor.rgb, vec3(1.0 / gamma));
}

View File

@@ -0,0 +1,63 @@
#version 330 core
out vec4 FragColor;
@include "light_struct"
const int DIRECTIONAL = 0;
const int POINT = 1;
const int SPOT = 2;
in vec2 outTexCoords;
flat in Light outLight[1];
uniform sampler2D diffuse, position, texcoord, normal, depth;
vec4 directionalLight(Light light) {
vec3 norm = normalize(texture(normal, outTexCoords).rgb);
vec3 diffuse_color = texture(diffuse, outTexCoords).rgb;
vec3 fragPos = texture(position, outTexCoords).rgb;
vec3 lightDir = normalize(light.position - fragPos);
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() {
FragColor = texture(diffuse, outTexCoords) * calculateLighting(outLight[0]);
}

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,28 @@
# version 330 core
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inNormal;
layout(location = 3) in vec3 inUV;
uniform mat4 projection, view, model;
uniform int hasTexture;
uniform vec3 cameraPos;
out vec3 outPosition, outColor, outNormal, outUV;
out vec3 outFragPos, outViewPos, outCameraPos;
flat out int outHasTexture;
void main() {
// projection * view * model * position
outPosition = inPosition;
outColor = inColor;
outNormal= normalize(transpose(inverse(mat3(model))) * inNormal);
outUV = inUV;
outHasTexture = hasTexture;
outCameraPos = cameraPos;
outFragPos = vec3(model * vec4(inPosition, 1.0));
gl_Position = projection * view * model * vec4(inPosition, 1.0);
}

View File

@@ -0,0 +1,17 @@
#version 330 core
@include "light_struct"
layout (location = 0) in vec3 inPosition;
layout (location = 1) in vec2 inTexCoords;
uniform sampler2D diffuse, position, texcoord, normal, depth;
uniform Light light[1];
out vec2 outTexCoords;
flat out Light outLight[1];
void main() {
gl_Position = vec4(inPosition.x, inPosition.y, inPosition.z, 1.0);
outTexCoords = inTexCoords;
outLight = light;
}

BIN
assets/textures/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -27,13 +27,12 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = %w[lib assets]
spec.add_dependency "clipboard", "~> 1.3.5"
spec.add_dependency "excon", "~> 0.78.0"
spec.add_dependency "gosu", "~> 1.0.0"
spec.add_dependency "excon", "~> 0.88"
spec.add_dependency "gosu", "~> 1.1"
spec.add_dependency "gosu_more_drawables", "~> 0.3"
# spec.add_dependency "ffi", :platforms => [:mswin, :mingw] # Required by Clipboard on Windows
spec.add_development_dependency "bundler", "~> 1.16"
spec.add_development_dependency "bundler", "~> 2.2"
spec.add_development_dependency "minitest", "~> 5.0"
spec.add_development_dependency "rake", "~> 13.0"
end

View File

@@ -1,15 +1,13 @@
CYBERARM_ENGINE_ROOT_PATH = File.expand_path("..", __dir__)
begin
require File.expand_path("../../ffi-gosu/lib/gosu", File.dirname(__FILE__))
rescue LoadError => e
pp e
if ARGV.join.include?("--ffi-gosu")
require File.expand_path("../../ffi-gosu/lib/gosu", __dir__)
else
require "gosu"
end
require "json"
require "excon"
require "gosu_more_drawables"
require "clipboard"
require_relative "cyberarm_engine/version"
require_relative "cyberarm_engine/stats"
@@ -24,12 +22,19 @@ require_relative "cyberarm_engine/vector"
require_relative "cyberarm_engine/transform"
require_relative "cyberarm_engine/ray"
require_relative "cyberarm_engine/background"
require_relative "cyberarm_engine/background_nine_slice"
require_relative "cyberarm_engine/background_image"
require_relative "cyberarm_engine/animator"
require_relative "cyberarm_engine/text"
require_relative "cyberarm_engine/timer"
require_relative "cyberarm_engine/config_file"
require_relative "cyberarm_engine/console"
require_relative "cyberarm_engine/console/command"
require_relative "cyberarm_engine/console/subcommand"
require_relative "cyberarm_engine/console/commands/help_command"
require_relative "cyberarm_engine/ui/dsl"
require_relative "cyberarm_engine/ui/theme"
@@ -37,7 +42,7 @@ require_relative "cyberarm_engine/ui/event"
require_relative "cyberarm_engine/ui/style"
require_relative "cyberarm_engine/ui/border_canvas"
require_relative "cyberarm_engine/ui/element"
require_relative "cyberarm_engine/ui/elements/label"
require_relative "cyberarm_engine/ui/elements/text_block"
require_relative "cyberarm_engine/ui/elements/button"
require_relative "cyberarm_engine/ui/elements/toggle_button"
require_relative "cyberarm_engine/ui/elements/list_box"
@@ -62,3 +67,5 @@ require_relative "cyberarm_engine/model/model_object"
require_relative "cyberarm_engine/model/parser"
require_relative "cyberarm_engine/model/parsers/wavefront_parser"
require_relative "cyberarm_engine/model/parsers/collada_parser" if defined?(Nokogiri)
require_relative "cyberarm_engine/builtin/intro_state"

View File

@@ -1,11 +1,11 @@
module CyberarmEngine
class Animator
DEFAULT_TWEEN = :linear
def initialize(start_time:, duration:, from:, to:, &block)
def initialize(start_time:, duration:, from:, to:, tween: :linear, &block)
@start_time = start_time
@duration = duration
@from = from.dup
@to = to.dup
@tween = tween
@block = block
end
@@ -14,18 +14,18 @@ module CyberarmEngine
end
def progress
(@start_time.to_f + (Gosu.milliseconds - @start_time)) / (@start_time + @duration.to_f)
((Gosu.milliseconds - @start_time) / @duration.to_f).clamp(0.0, 1.0)
end
def complete?
progress >= 1.0
end
def transition(from, to, tween = DEFAULT_TWEEN)
def transition(from = @from, to = @to, tween = @tween)
from + (to - from) * send("tween_#{tween}", progress)
end
def color_transition(from, to, _tween = DEFAULT_TWEEN)
def color_transition(from = @from, to = @to, _tween = @tween)
r = transition(from.red, to.red)
g = transition(from.green, to.green)
b = transition(from.blue, to.blue)
@@ -34,7 +34,7 @@ module CyberarmEngine
Gosu::Color.rgba(r, g, b, a)
end
def color_hsv_transition(from, to, tween = DEFAULT_TWEEN)
def color_hsv_transition(from = @from, to = @to, tween = @tween)
hue = transition(from.hue, to.hue, tween)
saturation = transition(from.saturation, to.saturation, tween)
value = transition(from.value, to.value, tween)
@@ -43,14 +43,177 @@ module CyberarmEngine
Gosu::Color.from_ahsv(alpha, hue, saturation, value)
end
# NOTE: Use this for future reference? https://github.com/danro/easing-js/blob/master/easing.js
# Tween functions based on those provided here: https://github.com/danro/easing-js/blob/master/easing.js
# Under MIT / BSD
def tween_linear(t)
t
end
def tween_sine(t)
Math.sin(t) * t
def tween_ease_in_quad(t)
t ** 2
end
def tween_ease_out_quad(t)
-((t - 1) ** 2) -1
end
def tween_ease_in_out_quad(t)
return 0.5 * (t ** 2) if (t /= 0.5) < 1
return -0.5 * ((t -= 2) * t - 2)
end
def tween_ease_in_cubic(t)
t ** 3
end
def tween_ease_out_cubic(t)
((t - 1) ** 3) + 1
end
def tween_ease_in_out_cubic(t)
return 0.5 * (t ** 3) if ((t /= 0.5) < 1)
return 0.5 * ((t - 2) ** 3) + 2
end
def tween_ease_in_quart(t)
t ** 4
end
def tween_ease_out_quart(t)
-((t - 1) ** 4) - 1
end
def tween_ease_in_out_quart(t)
return 0.5 * (t ** 4) if ((t /= 0.5) < 1)
return -0.5 * ((t -= 2) * (t ** 3) - 2)
end
def tween_ease_in_quint(t)
t ** 5
end
def tween_ease_out_quint(t)
((t - 1) ** 5) + 1
end
def tween_ease_in_out_quint(t)
return 0.5 * (t ** 5) if ((t /= 0.5) < 1)
return 0.5 * ((t - 2) ** 5) + 2
end
def tween_ease_in(t) # sine
-Math.cos(t * (Math::PI / 2)) + 1
end
def tween_ease_out(t) # sine
Math.sin(t * (Math::PI / 2))
end
def tween_ease_in_out(t) # sine
(-0.5 * (Math.cos(Math::PI * t) - 1))
end
def tween_ease_in_expo(t)
(t == 0) ? 0 : 2 ** 10 * (t - 1)
end
def tween_ease_out_expo(t)
(t == 1) ? 1 : -(2 ** -10 * t) + 1
end
def tween_ease_in_out_expo(t)
return 0 if (t == 0)
return 1 if (t == 1)
return 0.5 * (2 ** 10 * (t - 1)) if ((t /= 0.5) < 1)
return 0.5 * (-(2 ** -10 * (t -= 1)) + 2)
end
def tween_ease_in_circ(t)
-(Math.sqrt(1 - (t * t)) - 1)
end
def tween_ease_out_circ(t)
Math.sqrt(1 - ((t - 1) ** 2))
end
def tween_ease_in_out_circ(t)
return -0.5 * (Math.sqrt(1 - t * t) - 1) if ((t /= 0.5) < 1)
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1)
end
def tween_ease_in_back(t)
s = 1.70158
t * t * ((s + 1) * t - s)
end
def tween_ease_out_back(t)
s = 1.70158
(t = t - 1) * t * ((s + 1) * t + s) + 1
end
def tween_ease_in_out_back(t)
s = 1.70158
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)) if ((t /= 0.5) < 1)
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2)
end
def tween_elastic(t)
-1 * (4 ** (-8 * t)) * Math.sin((t * 6 - 1) * (2 * Math::PI) / 2) + 1
end
def tween_swing_from_to(t)
s = 1.70158
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)) if (t /= 0.5) < 1
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2)
end
def tween_swing_from(t)
s = 1.70158;
t * t * ((s + 1) * t - s)
end
def tween_swing_to(t)
s = 1.70158
(t -= 1) * t * ((s + 1) * t + s) + 1
end
def tween_bounce(t)
if (t < (1 / 2.75))
(7.5625 * t * t)
elsif (t < (2 / 2.75))
(7.5625 * (t -= (1.5 / 2.75)) * t + 0.75)
elsif (t < (2.5 / 2.75))
(7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375)
else
(7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375)
end
end
def tween_bounce_past(t)
if (t < (1 / 2.75))
# missing "2 -"?
(7.5625 * t * t)
elsif (t < (2 / 2.75))
2 - (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75)
elsif (t < (2.5 / 2.75))
2 - (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375)
else
2 - (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375)
end
end
def tween_ease_from_to(t)
return 0.5 * (t ** 4) if ((t /= 0.5) < 1)
return -0.5 * ((t -= 2) * (t ** 3) - 2)
end
def tween_ease_from(t)
t ** 4
end
def tween_ease_to(t)
t ** 0.25
end
end
end

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

@@ -1,10 +1,11 @@
module CyberarmEngine
class BackgroundNineSlice
include CyberarmEngine::Common
attr_accessor :x, :y, :z, :width, :height
attr_accessor :x, :y, :z, :width, :height, :left, :top, :right, :bottom, :mode, :color
attr_reader :image
def initialize(image_path:, x: 0, y: 0, z: 0, width: 64, height: 64, mode: :tiled, left: 4, top: 4, right: 56, bottom: 56)
@image = get_image(image_path)
def initialize(image_path: nil, x: 0, y: 0, z: 0, width: 0, height: 0, mode: :tiled, left: 1, top: 1, right: 1, bottom: 1, color: Gosu::Color::WHITE)
@image = get_image(image_path) if image_path
@x = x
@y = y
@@ -20,23 +21,33 @@ module CyberarmEngine
@right = right
@bottom = bottom
nine_slice
@color = color
nine_slice if @image
end
def image=(image_path)
old_image = @image
@image = image_path ? get_image(image_path) : image_path
nine_slice if @image && old_image != @image
end
def nine_slice
@segment_top_left = Gosu.render(@left, @top) { @image.draw(0, 0, 0) }
@segment_top_right = Gosu.render(@image.width - @right, @top) { @image.draw(-@right, 0, 0) }
# pp [@left, @top, @right, @bottom, @image.width]
@segment_left = Gosu.render(@left, @bottom - @top) { @image.draw(0, -@top, 0) }
@segment_right = Gosu.render(@image.width - @right, @bottom - @top) { @image.draw(-@right, -@top, 0) }
@segment_top_left = @image.subimage(0, 0, @left, @top)
@segment_top_right = @image.subimage(@image.width - @right, 0, @right, @top)
@segment_bottom_left = Gosu.render(@left, @image.height - @bottom) { @image.draw(0, -@bottom, 0) }
@segment_bottom_right = Gosu.render(@image.width - @right, @image.height - @bottom) { @image.draw(-@right, -@bottom, 0) }
@segment_left = @image.subimage(0, @top, @left, @image.height - (@top + @bottom))
@segment_right = @image.subimage(@image.width - @right, @top, @left, @image.height - (@top + @bottom))
@segment_top = Gosu.render(@right - @left, @top) { @image.draw(-@left, 0, 0) }
@segment_bottom = Gosu.render(@right - @left, @image.height - @bottom) { @image.draw(-@left, -@bottom, 0) }
@segment_bottom_left = @image.subimage(0, @image.height - @bottom, @left, @bottom)
@segment_bottom_right = @image.subimage(@image.width - @right, @image.height - @bottom, @right, @bottom)
@segment_middle = Gosu.render(@right - @left, @bottom - @top) { @image.draw(-@left, -@top, 0) }
@segment_top = @image.subimage(@left, 0, @image.width - (@left + @right), @top)
@segment_bottom = @image.subimage(@left, @image.height - @bottom, @image.width - (@left + @right), @bottom)
@segment_middle = @image.subimage(@left, @top, @image.width - (@left + @right), @image.height - (@top + @bottom))
end
def cx
@@ -56,67 +67,73 @@ module CyberarmEngine
end
def width_scale
width_scale = (@width - (@left + (@image.width - @right))).to_f / (@right - @left)
scale = (@width.to_f - (@left + @right)) / (@image.width - (@left + @right))
scale.abs
end
def height_scale
height_scale = (@height - (@top + (@image.height - @bottom))).to_f / (@bottom - @top)
scale = (@height - (@top + @bottom)).to_f / (@image.height - (@top + @bottom))
scale.abs
end
def draw
return unless @image && @segment_top_left
@mode == :tiled ? draw_tiled : draw_stretched
end
def draw_stretched
@segment_top_left.draw(@x, @y, @z)
@segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale) # SCALE X
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z)
@segment_top_left.draw(@x, @y, @z, 1, 1, @color)
@segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale, 1, @color) # SCALE X
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color)
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale) # SCALE Y
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z)
@segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale) # SCALE X
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z)
@segment_left.draw(@x, @y + @top, @z, 1, height_scale) # SCALE Y
@segment_middle.draw(@x + @segment_top_left.width, @y + @segment_top.height, @z, width_scale, height_scale) # SCALE X and SCALE Y
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale, @color) # SCALE Y
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color)
@segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale, 1, @color) # SCALE X
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color)
@segment_left.draw(@x, @y + @top, @z, 1, height_scale, @color) # SCALE Y
@segment_middle.draw(@x + @segment_top_left.width, @y + @segment_top.height, @z, width_scale, height_scale, @color) # SCALE X and SCALE Y
end
def draw_tiled
@segment_top_left.draw(@x, @y, @z)
@segment_top_left.draw(@x, @y, @z, 1, 1, @color)
# p [width_scale, height_scale]
Gosu.clip_to(@x + @segment_top_left.width, @y, @segment_top.width * width_scale, @segment_top.height) do
width_scale.ceil.times do |i|
@segment_top.draw(@x + @segment_top_left.width + (@segment_top.width * i), @y, @z) # SCALE X
@segment_top.draw(@x + @segment_top_left.width + (@segment_top.width * i), @y, @z, 1, 1, @color) # SCALE X
end
end
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z)
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color)
Gosu.clip_to(@x + @width - @segment_top_right.width, @y + @top, @segment_right.width, @segment_right.height * height_scale) do
height_scale.ceil.times do |i|
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top + (@segment_right.height * i), @z) # SCALE Y
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top + (@segment_right.height * i), @z, 1, 1, @color) # SCALE Y
end
end
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z)
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color)
Gosu.clip_to(@x + @segment_top_left.width, @y + @height - @segment_bottom.height, @segment_top.width * width_scale, @segment_bottom.height) do
width_scale.ceil.times do |i|
@segment_bottom.draw(@x + @segment_bottom_left.width + (@segment_bottom.width * i), (@y + @height) - @segment_bottom.height, @z) # SCALE X
@segment_bottom.draw(@x + @segment_bottom_left.width + (@segment_bottom.width * i), (@y + @height) - @segment_bottom.height, @z, 1, 1, @color) # SCALE X
end
end
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z)
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color)
Gosu.clip_to(@x, @y + @top, @segment_left.width, @segment_left.height * height_scale) do
height_scale.ceil.times do |i|
@segment_left.draw(@x, @y + @top + (@segment_left.height * i), @z) # SCALE Y
@segment_left.draw(@x, @y + @top + (@segment_left.height * i), @z, 1, 1, @color) # SCALE Y
end
end
Gosu.clip_to(@x + @segment_top_left.width, @y + @segment_top.height, @width - (@segment_left.width + @segment_right.width), @height - (@segment_top.height + @segment_bottom.height)) do
height_scale.ceil.times do |y|
width_scale.ceil.times do |x|
@segment_middle.draw(@x + @segment_top_left.width + (@segment_middle.width * x), @y + @segment_top.height + (@segment_middle.height * y), @z) # SCALE X and SCALE Y
@segment_middle.draw(@x + @segment_top_left.width + (@segment_middle.width * x), @y + @segment_top.height + (@segment_middle.height * y), @z, 1, 1, @color) # SCALE X and SCALE Y
end
end
end

View File

@@ -0,0 +1,131 @@
module CyberarmEngine
class IntroState < CyberarmEngine::GameState
def setup
@display_width = 800
@display_height = 600
@title_size = 56
@caption_size = 24
@title = CyberarmEngine::Text.new("", size: @title_size, shadow_color: 0xaa_222222, static: true)
@caption = CyberarmEngine::Text.new("", size: @caption_size, shadow_color: 0xaa_222222, static: true)
@spacer_width = 256
@spacer_height = 6
@padding = 6
@cyberarm_engine_logo = get_image "#{CYBERARM_ENGINE_ROOT_PATH}/assets/textures/logo.png"
@gosu_logo = generate_proxy("Gosu", "Game Library", 0xff_111111)
@ruby_logo = generate_proxy("Ruby", "Programming Language", 0xff_880000)
@opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if defined?(OpenGL)
base_time = Gosu.milliseconds
@born_time = Gosu.milliseconds
@continue_after = 5_000
@animators = [
Animator.new(start_time: base_time += 1000, duration: 100, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time += -500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time + 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: Gosu.milliseconds + @continue_after - 1_000, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: Gosu.milliseconds + 250, duration: 500, from: 0.0, to: 1.0, tween: :swing_to) # CyberarmEngine LOGO
]
end
def draw
Gosu.draw_rect(0, 0, window.width, window.height, 0xff_222222)
scale = (@display_width - @padding * 2).to_f / @cyberarm_engine_logo.width * @animators.last.transition
@cyberarm_engine_logo.draw_rot(
window.width / 2,
(window.height) / 2 - @cyberarm_engine_logo.height / 2 - @padding * 2,
2,
0,
0.5,
0.5,
scale,
scale
)
Gosu.draw_rect(
window.width / 2 - (@display_width / 2 + @padding),
window.height / 2 - @spacer_height / 2,
@display_width + @padding,
@spacer_height * @animators[0].transition,
Gosu::Color::WHITE
)
@title.x = window.width / 2 - @title.text_width / 2
@title.y = (window.height / 2 + (@spacer_height / 2) + @padding) * @animators[1].transition
@title.text = "Powered By"
Gosu.clip_to(0, window.height / 2 + (@spacer_height / 2), window.width, @title.height) do
@title.draw
end
y = @title.y + @title.height * 2
Gosu.clip_to(0, y, window.width, @gosu_logo.height) do
Gosu.translate(@opengl_logo.nil? ? @ruby_logo.width / 2 : 0, 0) do
@gosu_logo.draw(
window.width.to_f / 2 - @ruby_logo.width / 2 - (@ruby_logo.width - @padding),
y * @animators[2].transition,
2
)
@ruby_logo.draw(
window.width.to_f / 2 - @ruby_logo.width / 2,
y * @animators[3].transition,
2
)
@opengl_logo&.draw(
window.width.to_f / 2 - @ruby_logo.width / 2 + (@ruby_logo.width - @padding),
y * @animators[4].transition,
2
)
end
end
Gosu.draw_rect(0, 0, window.width, window.height, Gosu::Color.rgba(0, 0, 0, 255 * @animators[5].transition), 10_000)
end
def update
@animators.each(&:update)
return unless Gosu.milliseconds - @born_time >= @continue_after
pop_state
push_state(@options[:forward], @options[:forward_options] || {}) if @options[:forward]
end
def button_down(_id)
@continue_after = 0
end
def generate_proxy(title, caption, color_hint)
@title.text = title
@caption.text = caption
width = @spacer_width + 2 * @padding
height = @title_size + @caption_size + @spacer_height + 2 * @padding + @spacer_height
Gosu.record(width.ceil, height.ceil) do
@title.x = (width - @padding * 2) / 2 - @title.text_width / 2
@title.y = @padding
@title.draw
Gosu.draw_rect(0, @padding + @title_size + @padding, @spacer_width, @spacer_height, Gosu::Color::WHITE)
Gosu.draw_rect(1, @padding + @title_size + @padding + 1, @spacer_width - 2, @spacer_height - 2, color_hint)
@caption.x = (width - @padding * 2) / 2 - @caption.text_width / 2
@caption.y = @padding + @title_size + @padding + @spacer_height + @padding
@caption.draw
end
end
end
end

View File

@@ -8,14 +8,21 @@ module CyberarmEngine
window.current_state
end
def previous_state
window.previous_state
def previous_state(state = nil)
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
def pop_state
window.pop_state
end
def shift_state
window.shift_state
end
def show_cursor
window.show_cursor
end
@@ -24,8 +31,20 @@ module CyberarmEngine
window.show_cursor = boolean
end
def draw_rect(x, y, width, height, color, z = 0)
Gosu.draw_rect(x, y, width, height, color, z)
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)
Gosu.draw_rect(x, y, width, height, color, z, mode)
end
def fill(color, z = 0)
@@ -70,6 +89,7 @@ module CyberarmEngine
else
klass.new(path)
end
hash[path] = instance
asset = instance
end
@@ -90,7 +110,19 @@ module CyberarmEngine
end
def window
$window
CyberarmEngine::Window.instance
end
def control_down?
Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
end
def shift_down?
Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
end
def alt_down?
Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
end
end
end

View File

@@ -0,0 +1,248 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
Z = 100_000
PADDING = 2
include Common
attr_reader :text_input
def initialize(font: Gosu.default_font_name)
@text_input = Gosu::TextInput.new
@width = window.width / 4 * 3
@height = window.height / 4 * 3
@input = Text.new("", x: 4, y: @height - (PADDING * 2), z: Console::Z + 1, font: font)
@input.y -= @input.height
@history = Text.new("", x: 4, z: Console::Z + 1, font: font, border: true, border_color: Gosu::Color::BLACK)
update_history_y
@command_history = []
@command_history_index = 0
@memory = ""
@background_color = Gosu::Color.rgba(0, 0, 0, 200)
@foreground_color = Gosu::Color.rgba(100, 100, 100, 100)
@input_color = Gosu::Color.rgba(100, 100, 100, 200)
@showing_cursor = false
@active_text_input = nil
@show_caret = true
@caret_last_change = Gosu.milliseconds
@caret_interval = 250
@caret_color = Gosu::Color::WHITE
@selection_color = Gosu::Color.new(0x5522ff22)
end
def draw
# Background/Border
draw_rect(0, 0, @width, @height, @background_color, Console::Z)
# Foregound/History
draw_rect(PADDING, PADDING, @width - (PADDING * 2), @height - (PADDING * 2), @foreground_color, Console::Z)
# Text bar
draw_rect(2, @input.y, @width - (PADDING * 2), @input.height, @input_color, Console::Z)
@history.draw
@input.draw
# Caret
if @show_caret
draw_rect(@input.x + caret_from_left, @input.y, Console::PADDING, @input.height, @caret_color, Console::Z + 2)
end
# Caret selection
if caret_start != caret_end
if caret_start < @text_input.selection_start
draw_rect(@input.x + caret_from_left, @input.y, caret_selection_width, @input.height, @selection_color, Console::Z)
else
draw_rect((@input.x + caret_from_left) - caret_selection_width, @input.y, caret_selection_width, @input.height, @selection_color, Console::Z)
end
end
end
def caret_from_left
return 0 if @text_input.caret_pos.zero?
@input.textobject.text_width(@text_input.text[0..@text_input.caret_pos - 1])
end
def caret_selection_width
@input.textobject.text_width(@text_input.text[caret_start..(caret_end - 1)])
end
def caret_pos
@text_input.caret_pos
end
def caret_start
@text_input.selection_start < @text_input.caret_pos ? @text_input.selection_start : @text_input.caret_pos
end
def caret_end
@text_input.selection_start > @text_input.caret_pos ? @text_input.selection_start : @text_input.caret_pos
end
def update
if Gosu.milliseconds - @caret_last_change >= @caret_interval
@caret_last_change = Gosu.milliseconds
@show_caret = !@show_caret
end
if @width != window.width || @height != @height
@width = window.width / 4 * 3
@height = window.height / 4 * 3
@input.y = @height - (PADDING * 2 + @input.height)
update_history_y
end
@input.text = @text_input.text
end
def button_down(id)
case id
when Gosu::KbEnter, Gosu::KbReturn
return unless @text_input.text.length.positive?
@history.text += "\n<c=999999>> #{@text_input.text}</c>"
@command_history << @text_input.text
@command_history_index = @command_history.size
update_history_y
handle_command
@text_input.text = ""
when Gosu::KbUp
@command_history_index -= 1
@command_history_index = 0 if @command_history_index.negative?
@text_input.text = @command_history[@command_history_index]
when Gosu::KbDown
@command_history_index += 1
if @command_history_index > @command_history.size - 1
@text_input.text = "" unless @command_history_index > @command_history.size
@command_history_index = @command_history.size
else
@text_input.text = @command_history[@command_history_index]
end
when Gosu::KbTab
split = @text_input.text.split(" ")
if !@text_input.text.end_with?(" ") && split.size == 1
list = abbrev_search(Console::Command.list_commands.map { |cmd| cmd.command.to_s }, @text_input.text)
if list.size == 1
@text_input.text = "#{list.first} "
elsif list.size.positive?
stdin("\n#{list.map { |cmd| Console::Style.highlight(cmd) }.join(', ')}")
end
elsif split.size.positive? && cmd = Console::Command.find(split.first)
cmd.autocomplete(self)
end
when Gosu::KbBacktick
# Remove backtick character from input
@text_input.text = if @text_input.text.size > 1
@text_input.text[0..@text_input.text.size - 2]
else
""
end
# Copy
when Gosu::KbC
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
p @memory
elsif control_down?
@text_input.text = ""
end
# Paste
when Gosu::KbV
if control_down? && shift_down?
string = @text_input.text.chars.insert(caret_pos, @memory).join
_caret_pos = caret_pos
@text_input.text = string
@text_input.caret_pos = _caret_pos + @memory.length
@text_input.selection_start = _caret_pos + @memory.length
end
# Cut
when Gosu::KbX
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
string = @text_input.text.chars
Array(caret_start..caret_end - 1).each_with_index do |i, j|
string.delete_at(i - j)
end
@text_input.text = string.join
end
# Delete word to left of caret
when Gosu::KbW
if control_down?
split = @text_input.text.split(" ")
split.delete(split.last)
@text_input.text = split.join(" ")
end
# Clear history
when Gosu::KbL
@history.text = "" if control_down?
end
end
def button_up(id)
end
def update_history_y
@history.y = @height - (PADDING * 2) - @input.height - (@history.text.lines.count * @history.textobject.height)
end
def handle_command
string = @text_input.text
split = string.split(" ")
command = split.first
arguments = split.length.positive? ? split[1..split.length - 1] : []
CyberarmEngine::Console::Command.use(command, arguments, self)
end
def abbrev_search(array, text)
return [] unless text.length.positive?
list = []
Abbrev.abbrev(array).each do |abbrev, value|
next unless abbrev&.start_with?(text)
list << value
end
list.uniq
end
def stdin(string)
@history.text += "\n#{string}"
update_history_y
end
def focus
@active_text_input = window.text_input
window.text_input = @text_input
@showing_cursor = window.needs_cursor
window.needs_cursor = true
@show_caret = true
@caret_last_change = Gosu.milliseconds
end
def blur
window.text_input = @active_text_input
window.needs_cursor = @showing_cursor
end
end
end

View File

@@ -0,0 +1,158 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
module Style
def self.error(string)
"<c=ff5555>#{string}</c>"
end
def self.warn(string)
"<c=ff7700>#{string}</c>"
end
def self.notice(string)
"<c=55ff55>#{string}</c>"
end
def self.highlight(string, color = "5555ff")
"<c=#{color}>#{string}</c>"
end
end
class Command
def self.inherited(subclass)
@list ||= []
@commands ||= []
@list << subclass
end
def self.setup
@list ||= []
@commands = []
@list.each do |subclass|
cmd = subclass.new
if @commands.detect { |c| c.command == cmd.command }
raise "Command '#{cmd.command}' from '#{cmd.class}' already exists!"
end
@commands << cmd
end
end
def self.use(command, arguments, console)
found_command = @commands.detect { |cmd| cmd.command == command.to_sym }
if found_command
found_command.handle(arguments, console)
else
console.stdin("Command #{Style.error(command)} not found.")
end
end
def self.find(command)
@commands.detect { |cmd| cmd.command == command.to_sym }
end
def self.list_commands
@commands
end
def initialize
@store = {}
@subcommands = []
setup
end
def setup
end
def subcommand(command, type)
if @subcommands.detect { |subcmd| subcmd.command == command.to_sym }
raise "Subcommand '#{command}' for '#{self.command}' already exists!"
end
@subcommands << SubCommand.new(self, command, type)
end
def get(key)
@store[key]
end
def set(key, value)
@store[key] = value
end
def group
raise NotImplementedError
end
def command
raise NotImplementedError
end
def handle(arguments, console)
raise NotImplementedError
end
def autocomplete(console)
split = console.text_input.text.split(" ")
if @subcommands.size.positive?
if !console.text_input.text.end_with?(" ") && split.size == 2
list = console.abbrev_search(@subcommands.map { |cmd| cmd.command.to_s }, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{list.first} "
else
return unless list.size.positive?
console.stdin(list.map { |cmd| Console::Style.highlight(cmd) }.join(", ").to_s)
end
# List available options on subcommand
elsif (console.text_input.text.end_with?(" ") && split.size == 2) || !console.text_input.text.end_with?(" ") && split.size == 3
subcommand = @subcommands.detect { |cmd| cmd.command.to_s == (split[1]) }
if subcommand
if split.size == 2
console.stdin("Available options: #{subcommand.values.map { |value| Console::Style.highlight(value) }.join(',')}")
else
list = console.abbrev_search(subcommand.values, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{split[1]} #{list.first} "
elsif list.size.positive?
console.stdin("Available options: #{list.map { |value| Console::Style.highlight(value) }.join(',')}")
end
end
end
# List available subcommands if command was entered and has only a space after it
elsif console.text_input.text.end_with?(" ") && split.size == 1
console.stdin("Available subcommands: #{@subcommands.map { |cmd| Console::Style.highlight(cmd.command) }.join(', ')}")
end
end
end
def handle_subcommand(arguments, console)
if arguments.size.zero?
console.stdin(usage)
return
end
subcommand = arguments.delete_at(0)
found_command = @subcommands.detect { |cmd| cmd.command == subcommand.to_sym }
if found_command
found_command.handle(arguments, console)
else
console.stdin("Unknown subcommand #{Style.error(subcommand)} for #{Style.highlight(command)}")
end
end
def usage
raise NotImplementedError
end
end
end
end

View File

@@ -0,0 +1,43 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
class HelpCommand < CyberarmEngine::Console::Command
def group
:global
end
def command
:help
end
def handle(arguments, console)
console.stdin(usage(arguments.first))
end
def autocomplete(console)
split = console.text_input.text.split(" ")
if !console.text_input.text.start_with?(" ") && split.size == 2
list = console.abbrev_search(Command.list_commands.map { |cmd| cmd.command.to_s }, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{list.first} "
elsif list.size > 1
console.stdin(list.map { |cmd| Style.highlight(cmd) }.join(", "))
end
end
end
def usage(command = nil)
if command
if cmd = Command.find(command)
cmd.usage
else
"#{Style.error(command)} is not a command"
end
else
"Available commands:\n#{Command.list_commands.map { |cmd| Style.highlight(cmd.command).to_s }.join(', ')}"
end
end
end
end
end

View File

@@ -0,0 +1,100 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
class Command
class SubCommand
def initialize(parent, command, type)
@parent = parent
@command = command
@type = type
end
attr_reader :command
def handle(arguments, console)
if arguments.size > 1
console.stdin("to many arguments for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.size)} expected #{Style.notice(1)}.")
return
end
case @type
when :boolean
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || false
console.stdin("#{command}: #{Style.highlight(var)}")
when "on"
var = @parent.set(command.to_sym, true)
console.stdin("#{command} => #{Style.highlight(var)}")
when "off"
var = @parent.set(command.to_sym, false)
console.stdin("#{command} => #{Style.highlight(var)}")
else
console.stdin("Invalid argument for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.last)} expected #{Style.notice('on')}, or #{Style.notice('off')}.")
end
when :string
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "\"\""
console.stdin("#{command}: #{Style.highlight(var)}")
else
var = @parent.set(command.to_sym, arguments.last)
console.stdin("#{command} => #{Style.highlight(var)}")
end
when :integer
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "nil"
console.stdin("#{command}: #{Style.highlight(var)}")
else
begin
var = @parent.set(command.to_sym, Integer(arguments.last))
console.stdin("#{command} => #{Style.highlight(var)}")
rescue ArgumentError
console.stdin("Error: #{Style.error("Expected an integer, got '#{arguments.last}'")}")
end
end
when :decimal
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "nil"
console.stdin("#{command}: #{Style.highlight(var)}")
else
begin
var = @parent.set(command.to_sym, Float(arguments.last))
console.stdin("#{command} => #{Style.highlight(var)}")
rescue ArgumentError
console.stdin("Error: #{Style.error("Expected a decimal or integer, got '#{arguments.last}'")}")
end
end
else
raise RuntimeError
end
end
def values
case @type
when :boolean
%w[on off]
else
[]
end
end
def usage
case @type
when :boolean
"#{Style.highlight(command)} #{Style.notice('[on|off]')}"
when :string
"#{Style.highlight(command)} #{Style.notice('[string]')}"
when :integer
"#{Style.highlight(command)} #{Style.notice('[0]')}"
when :decimal
"#{Style.highlight(command)} #{Style.notice('[0.0]')}"
end
end
end
end
end
end

View File

@@ -7,7 +7,7 @@ module CyberarmEngine
attr_reader :alpha
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
@image = options[:image] ? image(options[:image]) : nil
@@ -55,9 +55,9 @@ module CyberarmEngine
if $debug
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 != ""
$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)
@debug_text.draw
end
@@ -102,13 +102,13 @@ module CyberarmEngine
end
def _x_visible
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?((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)
end
def _y_visible
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?((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)
end
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)
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)
end
@@ -190,12 +186,12 @@ module CyberarmEngine
# Duplication... so DRY.
def each_circle_collision(object, _resolve_with = :width, &block)
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)
block.call(o, object) if distance <= radius + object.radius && block
end
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|
next if self == o
@@ -206,9 +202,9 @@ module CyberarmEngine
end
def destroy
if $window.current_state
$window.current_state.game_objects.each do |o|
$window.current_state.game_objects.delete(o) if o.is_a?(self.class) && o == self
if window.current_state
window.current_state.game_objects.each do |o|
window.current_state.game_objects.delete(o) if o.is_a?(self.class) && o == self
end
end
end
@@ -220,13 +216,13 @@ module CyberarmEngine
def self.each_circle_collision(object, _resolve_with = :width, &block)
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)
block.call(o, object) if distance <= o.radius + object.radius && block
end
else
lista = $window.current_state.game_objects.select { |i| i.instance_of?(self) }
listb = $window.current_state.game_objects.select { |i| i.instance_of?(object) }
lista = window.current_state.game_objects.select { |i| i.instance_of?(self) }
listb = window.current_state.game_objects.select { |i| i.instance_of?(object) }
lista.product(listb).each do |o, o2|
next if o == o2
@@ -238,9 +234,9 @@ module CyberarmEngine
def self.destroy_all
INSTANCES.clear
if $window.current_state
$window.current_state.game_objects.each do |o|
$window.current_state.game_objects.delete(o) if o.is_a?(self.class)
if window.current_state
window.current_state.game_objects.each do |o|
window.current_state.game_objects.delete(o) if o.is_a?(self.class)
end
end
end

View File

@@ -9,7 +9,7 @@ module CyberarmEngine
@options = options
@game_objects = []
@global_pause = false
$window.text_input = nil unless options[:preserve_text_input]
window.text_input = nil unless options[:preserve_text_input]
@down_keys = {}
end
@@ -17,6 +17,11 @@ module CyberarmEngine
def setup
end
# Called immediately after setup returns.
# GuiState uses this to set current_theme for ToolTip
def post_setup
end
def draw
@game_objects.each(&:draw)
end
@@ -25,48 +30,27 @@ module CyberarmEngine
@game_objects.each(&:update)
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
$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
)
def needs_redraw?
true
end
def destroy
@options.clear
@game_objects.clear
def needs_repaint?
true
end
def drop(filename)
end
def gamepad_connected(index)
end
def gamepad_disconnected(index)
end
def gain_focus
end
def lose_focus
end
def button_down(id)
@@ -85,6 +69,54 @@ module CyberarmEngine
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)
@game_objects << object
end

View File

@@ -3,7 +3,8 @@ module CyberarmEngine
attr_accessor :objects, :materials, :vertices, :uvs, :texures, :normals, :faces, :colors, :bones, :material_file,
:current_material, :current_object, :vertex_count, :smoothing
attr_reader :position, :bounding_box, :textured_material, :file_path, :positions_buffer_id, :colors_buffer_id,
:normals_buffer_id, :uvs_buffer_id, :textures_buffer_id, :vertex_array_id, :aabb_tree
:normals_buffer_id, :uvs_buffer_id, :textures_buffer_id, :vertex_array_id, :aabb_tree,
:vertices_count
def initialize(file_path:)
@file_path = file_path
@@ -23,6 +24,8 @@ module CyberarmEngine
@bones = []
@smoothing = 0
@vertices_count = 0
@bounding_box = BoundingBox.new
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
@@ -32,6 +35,8 @@ module CyberarmEngine
parse(parser)
@vertices_count = @vertices.size
@has_texture = false
@materials.each do |_key, material|

View File

@@ -11,7 +11,19 @@ module CyberarmEngine
if e != GL_NO_ERROR
warn "OpenGL error detected by handler at: #{caller[0]}"
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

View File

@@ -3,8 +3,8 @@ module CyberarmEngine
attr_accessor :position, :orientation, :aspect_ratio, :field_of_view,
:min_view_distance, :max_view_distance
def initialize(position:, aspect_ratio:, orientation: Vector.new(0, 0,
0), field_of_view: 70.0, min_view_distance: 0.1, max_view_distance: 155.0)
def initialize(position:, aspect_ratio:, orientation: Vector.new(0, 0, 0),
field_of_view: 70.0, min_view_distance: 0.1, max_view_distance: 1024.0)
@position = position
@orientation = orientation

View File

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

View File

@@ -3,12 +3,15 @@ module CyberarmEngine
@@immediate_mode_warning = false
attr_accessor :show_wireframe
attr_reader :number_of_vertices
def initialize(width:, height:, show_wireframe: false)
@width = width
@height = height
@show_wireframe = show_wireframe
@number_of_vertices = 0
@g_buffer = GBuffer.new(width: @width, height: @height)
end
@@ -20,6 +23,8 @@ module CyberarmEngine
end
def render(camera, lights, entities)
@number_of_vertices = 0
glViewport(0, 0, @width, @height)
glEnable(GL_DEPTH_TEST)
@@ -44,6 +49,8 @@ module CyberarmEngine
gl_error?
draw_model(entity.model, shader)
entity.draw
@number_of_vertices += entity.model.vertices_count
end
end
@@ -90,6 +97,8 @@ module CyberarmEngine
draw_mesh(entity.model)
entity.draw
glPopMatrix
@number_of_vertices += entity.model.vertices_count
end
end

View File

@@ -4,7 +4,7 @@ module CyberarmEngine
def initialize
@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
def draw(camera, lights, entities)

View File

@@ -54,7 +54,7 @@ module CyberarmEngine
texture_id = tex_names_buf.unpack1("L2")
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, array_of_pixels)
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, array_of_pixels)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) if @retro

View File

@@ -3,7 +3,9 @@ module CyberarmEngine
CACHE = {}
attr_accessor :x, :y, :z, :size, :options
attr_reader :text, :textobject, :factor_x, :factor_y, :color, :shadow, :shadow_size, :shadow_alpha, :shadow_color
attr_reader :text, :textobject, :factor_x, :factor_y, :color,
:border, :border_size, :border_alpha, :border_color,
:shadow, :shadow_size, :shadow_alpha, :shadow_color
def initialize(text, options = {})
@text = text.to_s || ""
@@ -15,16 +17,27 @@ module CyberarmEngine
@z = options[:z] || 1025
@factor_x = options[:factor_x] || 1
@factor_y = options[:factor_y] || 1
@color = options[:color] || Gosu::Color::WHITE
@mode = options[:mode] || :default
if options[:color]
@color = options[:color].is_a?(Gosu::Color) ? options[:color] : Gosu::Color.new(options[:color])
else
@color = Gosu::Color::WHITE
end
@mode = options[:mode] || :default
@alignment = options[:alignment] || nil
@shadow = true if options[:shadow] == true
@shadow = false if options[:shadow] == false
@shadow = true if options[:shadow].nil?
@shadow_size = options[:shadow_size] || 1
@border = options[:border]
@border = true if options[:border].nil?
@border_size = options[:border_size] || 1
@border_alpha = options[:border_alpha] || 30
@border_color = options[:border_color] || Gosu::Color::BLACK
@shadow = options[:shadow]
@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)
if @alignment
@@ -32,13 +45,11 @@ module CyberarmEngine
when :left
@x = 0 + BUTTON_PADDING
when :center
@x = ($window.width / 2) - (@textobject.text_width(@text) / 2)
@x = (CyberarmEngine::Window.instance.width / 2) - (@textobject.text_width(@text) / 2)
when :right
@x = $window.width - BUTTON_PADDING - @textobject.text_width(@text)
@x = CyberarmEngine::Window.instance.width - BUTTON_PADDING - @textobject.text_width(@text)
end
end
self
end
def check_cache(size, font_name)
@@ -65,85 +76,165 @@ module CyberarmEngine
font
end
def swap_font(size, font_name = @font)
if @size != size || @font != font_name
@size = size
@font = font_name
@textobject = check_cache(size, font_name)
end
end
def text=(string)
@rendered_shadow = nil
invalidate_cache! if @text != string
@text = string
end
def factor_x=(n)
@rendered_shadow = nil
invalidate_cache! if @factor_x != n
@factor_x = n
end
def factor_y=(n)
@rendered_shadow = nil
invalidate_cache! if @factor_y != n
@factor_y = n
end
def color=(color)
@rendered_shadow = nil
@color = color
old_color = @color
if color
@color = color.is_a?(Gosu::Color) ? color : Gosu::Color.new(color)
else
raise "color cannot be nil"
end
invalidate_cache! if old_color != color
end
def shadow=(boolean)
@rendered_shadow = nil
@shadow = boolean
def border=(boolean)
invalidate_cache! if @border != boolean
@border = boolean
end
def shadow_size=(n)
@rendered_shadow = nil
@shadow_size = n
def border_size=(n)
invalidate_cache! if @border_size != n
@border_size = n
end
def shadow_alpha=(n)
@rendered_shadow = nil
@shadow_alpha = n
def border_alpha=(n)
invalidate_cache! if @border_alpha != n
@border_alpha = n
end
def shadow_color=(n)
@rendered_shadow = nil
@shadow_color = n
def border_color=(n)
invalidate_cache! if @border_color != n
@border_color = n
end
def width(text = @text)
textobject.text_width(text)
markup_width(text)
end
def text_width(text = @text)
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
def markup_width(text = @text)
textobject.markup_width(text)
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
def height(text = @text)
text.lines.count > 0 ? text.lines.count * textobject.height : @textobject.height
if text.lines.count > 0
text.lines.count * textobject.height + @border_size + @shadow_size
else
@textobject.height + @border_size + @shadow_size
end
end
def draw(method = :draw_markup)
if @shadow && !ARGV.join.include?("--no-shadow")
shadow_alpha = @color.alpha <= 30 ? @color.alpha : @shadow_alpha
shadow_color = @shadow_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue,
shadow_alpha)
white = Gosu::Color::WHITE
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)
_x = @shadow_size
_y = @shadow_size
@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)
@rendered_shadow ||= Gosu.render((width + (shadow_size * 2)).ceil, (height + (@shadow_size * 2)).ceil) do
@textobject.send(method, @text, _x - @shadow_size, _y, @z, @factor_x, @factor_y, white, :add)
@textobject.send(method, @text, _x - @shadow_size, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add)
img.draw(0, -_y, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(_x, -_y, @z, @factor_x, @factor_y, @border_color, @mode)
@textobject.send(method, @text, _x, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.send(method, @text, _x + @shadow_size, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add)
img.draw(_x, 0, @z, @factor_x, @factor_y, @border_color, @mode)
img.draw(_x, _y, @z, @factor_x, @factor_y, @border_color, @mode)
@textobject.send(method, @text, _x, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.send(method, @text, _x - @shadow_size, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.send(method, @text, _x + @shadow_size, _y, @z, @factor_x, @factor_y, white, :add)
@textobject.send(method, @text, _x + @shadow_size, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add)
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
@rendered_shadow.draw(@x - @shadow_size, @y - @shadow_size, @z, @factor_x, @factor_y, shadow_color)
end
@textobject.send(method, @text, @x, @y, @z, @factor_x, @factor_y, @color, @mode)
@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")
border_alpha = @color.alpha <= 30 ? @color.alpha : @border_alpha
border_color = @border_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue,
border_alpha)
white = Gosu::Color::WHITE
_x = @border_size
_y = @border_size
_width = method == :draw_markup ? text_width : markup_width
@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 - @border_size, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x, _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)
@textobject.send(method, @text, _x, _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)
@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)
end
@cached_text_border_image.draw(@x - @border_size, @y - @border_size, @z, @factor_x, @factor_y, border_color)
end
if @shadow
shadow_color = @shadow_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue, @shadow_alpha)
@textobject.send(method, @text, @x + @shadow_size, @y + @shadow_size, @z, @factor_x, @factor_y, shadow_color, @mode)
end
@textobject.send(method, @text, @x, @y, @z, @factor_x, @factor_y, @color, @mode)
end
end
def alpha=(n)
@@ -156,5 +247,11 @@ module CyberarmEngine
def update
end
def invalidate_cache!
@cached_text_border_image = nil
@cached_text_shadow_image = nil
@gosu_cached_text_image = nil
end
end
end

View File

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

View File

@@ -8,11 +8,30 @@ module CyberarmEngine
container(CyberarmEngine::Element::Stack, options, &block)
end
# TODO: Remove in version 0.16.0+
def label(text, options = {}, &block)
options[:parent] = element_parent
options[:theme] = current_theme
add_element(Element::Label.new(text, options, block))
add_element(Element::TextBlock.new(text, options, block))
end
[
"Banner",
"Title",
"Subtitle",
"Tagline",
"Caption",
"Para",
"Inscription",
"Link"
].each do |const|
define_method(:"#{const.downcase}") do |text, options = {}, &block|
options[:parent] = element_parent
options[:theme] = current_theme
add_element(Element.const_get(const).new(text, options, block))
end
end
def button(text, options = {}, &block)
@@ -79,7 +98,7 @@ module CyberarmEngine
end
def background(color = Gosu::Color::NONE)
element_parent.style.background = color
element_parent.style.default[:background] = color
end
def theme(theme)
@@ -97,7 +116,7 @@ module CyberarmEngine
end
private def element_parent
$__current_container__
CyberarmEngine::Element::Container.current_container
end
private def container(klass, options = {}, &block)
@@ -107,12 +126,12 @@ module CyberarmEngine
_container = klass.new(options, block)
old_parent = element_parent
$__current_container__ = _container
CyberarmEngine::Element::Container.current_container = _container
_container.build
_container.parent.add(_container)
$__current_container__ = old_parent
CyberarmEngine::Element::Container.current_container = old_parent
_container
end

View File

@@ -4,7 +4,7 @@ module CyberarmEngine
include Event
include Common
attr_accessor :x, :y, :z, :enabled, :tip
attr_accessor :x, :y, :z, :tip, :element_visible
attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas
def initialize(options = {}, block = nil)
@@ -13,15 +13,18 @@ module CyberarmEngine
@options = options
@block = block
@focus = false
@enabled = true
@visible = true
@tip = @options[:tip] || ""
@focus = !@options.key?(:focus) ? false : @options[:focus]
@enabled = !@options.key?(:enabled) ? true : @options[:enabled]
@visible = !@options.key?(:visible) ? true : @options[:visible]
@tip = @options[:tip] || ""
@debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color]
@style = Style.new(options)
@root ||= nil
@gui_state ||= nil
@element_visible = true
@x = @style.x
@y = @style.y
@@ -34,23 +37,40 @@ module CyberarmEngine
@style.height = default(:height) || nil
@style.background_canvas = Background.new
@style.border_canvas = BorderCanvas.new(element: self)
@style.background_nine_slice_canvas = BackgroundNineSlice.new
@style.background_image_canvas = BackgroundImage.new
@style.border_canvas = BorderCanvas.new(element: self)
@style_event = :default
stylize
default_events
root.gui_state.request_focus(self) if @options[:autofocus]
end
def stylize
set_static_position
set_border_thickness(@style.border_thickness)
set_padding(@style.padding)
set_color
set_font
set_margin(@style.margin)
set_padding
set_margin
set_background(@style.background)
set_border_color(@style.border_color)
set_background
set_background_nine_slice
set_background_image
set_border_thickness
set_border_color
root.gui_state.request_repaint
end
def safe_style_fetch(*args)
@style.hash.dig(@style_event, *args) || @style.hash.dig(:default, *args) || default(*args)
end
def set_static_position
@@ -58,47 +78,100 @@ module CyberarmEngine
@y = @style.y if @style.y != 0
end
def set_background(background)
@style.background = background
@style.background_canvas.background = background
def set_color
@style.color = safe_style_fetch(:color)
@text&.color = @style.color
end
def set_border_thickness(border_thickness)
@style.border_thickness = border_thickness
@style.border_thickness_left = default(:border_thickness_left) || @style.border_thickness
@style.border_thickness_right = default(:border_thickness_right) || @style.border_thickness
@style.border_thickness_top = default(:border_thickness_top) || @style.border_thickness
@style.border_thickness_bottom = default(:border_thickness_bottom) || @style.border_thickness
def set_font
@text&.swap_font(safe_style_fetch(:text_size), safe_style_fetch(:font))
end
def set_border_color(color)
@style.border_color = color
def set_background
@style.background = safe_style_fetch(:background)
@style.border_color_left = default(:border_color_left) || @style.border_color
@style.border_color_right = default(:border_color_right) || @style.border_color
@style.border_color_top = default(:border_color_top) || @style.border_color
@style.border_color_bottom = default(:border_color_bottom) || @style.border_color
@style.border_canvas.color = color
@style.background_canvas.background = @style.background
end
def set_padding(padding)
@style.padding = padding
def set_background_nine_slice
@style.background_nine_slice = safe_style_fetch(:background_nine_slice)
@style.padding_left = default(:padding_left) || @style.padding
@style.padding_right = default(:padding_right) || @style.padding
@style.padding_top = default(:padding_top) || @style.padding
@style.padding_bottom = default(:padding_bottom) || @style.padding
@style.background_nine_slice_mode = safe_style_fetch(:background_nine_slice_mode) || :stretch
@style.background_nine_slice_color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE
@style.background_nine_slice_canvas.color = @style.background_nine_slice_color
@style.background_nine_slice_from_edge = safe_style_fetch(:background_nine_slice_from_edge)
@style.background_nine_slice_left = safe_style_fetch(:background_nine_slice_left) || @style.background_nine_slice_from_edge
@style.background_nine_slice_top = safe_style_fetch(:background_nine_slice_top) || @style.background_nine_slice_from_edge
@style.background_nine_slice_right = safe_style_fetch(:background_nine_slice_right) || @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
def set_margin(margin)
@style.margin = margin
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
@style.margin_left = default(:margin_left) || @style.margin
@style.margin_right = default(:margin_right) || @style.margin
@style.margin_top = default(:margin_top) || @style.margin
@style.margin_bottom = default(:margin_bottom) || @style.margin
def set_border_thickness
@style.border_thickness = safe_style_fetch(:border_thickness)
@style.border_thickness_left = safe_style_fetch(:border_thickness_left) || @style.border_thickness
@style.border_thickness_right = safe_style_fetch(:border_thickness_right) || @style.border_thickness
@style.border_thickness_top = safe_style_fetch(:border_thickness_top) || @style.border_thickness
@style.border_thickness_bottom = safe_style_fetch(:border_thickness_bottom) || @style.border_thickness
end
def set_border_color
@style.border_color = safe_style_fetch(:border_color)
@style.border_color_left = safe_style_fetch(:border_color_left) || @style.border_color
@style.border_color_right = safe_style_fetch(:border_color_right) || @style.border_color
@style.border_color_top = safe_style_fetch(:border_color_top) || @style.border_color
@style.border_color_bottom = safe_style_fetch(:border_color_bottom) || @style.border_color
@style.border_canvas.color = [
@style.border_color_top,
@style.border_color_right,
@style.border_color_bottom,
@style.border_color_left
]
end
def set_padding
@style.padding = safe_style_fetch(:padding)
@style.padding_left = safe_style_fetch(:padding_left) || @style.padding
@style.padding_right = safe_style_fetch(:padding_right) || @style.padding
@style.padding_top = safe_style_fetch(:padding_top) || @style.padding
@style.padding_bottom = safe_style_fetch(:padding_bottom) || @style.padding
end
def set_margin
@style.margin = safe_style_fetch(:margin)
@style.margin_left = safe_style_fetch(:margin_left) || @style.margin
@style.margin_right = safe_style_fetch(:margin_right) || @style.margin
@style.margin_top = safe_style_fetch(:margin_top) || @style.margin
@style.margin_bottom = safe_style_fetch(:margin_bottom) || @style.margin
end
def update_styles(event = :default)
old_width = width
old_height = height
@style_event = event
return if self.is_a?(ToolTip)
if old_width != width || old_height != height
root.gui_state.request_recalculate
end
stylize
end
def default_events
@@ -116,45 +189,155 @@ module CyberarmEngine
event(:hover)
event(:leave)
event(:focus)
event(:blur)
event(:changed)
end
def enter(_sender)
@focus = false unless window.button_down?(Gosu::MsLeft)
if !@enabled
update_styles(:disabled)
elsif @focus
update_styles(:active)
else
update_styles(:hover)
end
:handled
end
def left_mouse_button(_sender, _x, _y)
@focus = true
unless @enabled
update_styles(:disabled)
else
update_styles(:active)
end
window.current_state.focus = self
:handled
end
def released_left_mouse_button(sender, _x, _y)
enter(sender)
:handled
end
def clicked_left_mouse_button(_sender, _x, _y)
@block&.call(self) if @enabled && !self.is_a?(Container)
:handled
end
def leave(_sender)
if @enabled
update_styles
else
update_styles(:disabled)
end
:handled
end
def blur(_sender)
@focus = false
if @enabled
update_styles
else
update_styles(:disabled)
end
:handled
end
def enabled=(boolean)
root.gui_state.request_repaint if @enabled != boolean
@enabled = boolean
recalculate
@enabled
end
def enabled?
@enabled
end
def focused?
@focus
end
def visible?
@visible
end
def element_visible?
@element_visible
end
def toggle
@visible = !@visible
root.gui_state.request_recalculate
root.gui_state.request_repaint
end
def show
bool = visible?
@visible = true
root.gui_state.request_recalculate unless bool
root.gui_state.request_repaint unless bool
end
def hide
bool = visible?
@visible = false
root.gui_state.request_recalculate if bool
root.gui_state.request_repaint if bool
end
def draw
return unless visible?
return unless element_visible?
@style.background_canvas.draw
@style.background_nine_slice_canvas.draw
@style.background_image_canvas.draw
@style.border_canvas.draw
Gosu.clip_to(@x, @y, width, height) do
render
end
render
end
def debug_draw
return if defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT
Gosu.draw_line(
x, y, @debug_color,
x + outer_width, y, @debug_color,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y, @debug_color,
x + outer_width, y + outer_height, @debug_color,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y + outer_height, @debug_color,
x, y + outer_height, @debug_color,
Float::INFINITY
)
Gosu.draw_line(
x, outer_height, @debug_color,
x, y, @debug_color,
Float::INFINITY
)
end
def update
@@ -226,20 +409,90 @@ module CyberarmEngine
(@style.border_thickness_top + @style.padding_top) + (@style.padding_bottom + @style.border_thickness_bottom)
end
def dimensional_size(size, dimension)
raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
def scroll_width
@children.sum(&:outer_width)
end
if size && size.is_a?(Numeric)
if size.between?(0.0, 1.0)
((@parent.send(:"content_#{dimension}") - send(:"noncontent_#{dimension}")) * size).round
else
size
def scroll_height
if is_a?(CyberarmEngine::Element::Flow)
return 0 if @children.size.zero?
pairs_ = []
sorted_children_ = @children.sort_by(&:y)
a_ = []
y_position_ = sorted_children_.first.y
sorted_children_.each do |child|
unless child.y == y_position_
y_position_ = child.y
pairs_ << a_
a_ = []
end
a_ << child
end
pairs_ << a_ unless pairs_.last == a_
pairs_.sum { |pair| + @style.padding_top + @style.border_thickness_top + pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom
else
@style.padding_top + @style.border_thickness_top + @children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom
end
end
def max_scroll_width
scroll_width - outer_width
end
def max_scroll_height
scroll_height - outer_height
end
def dimensional_size(size, dimension)
raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
new_size = if size.is_a?(Float) && size.between?(0.0, 1.0)
(@parent.send(:"content_#{dimension}") * size).floor - send(:"noncontent_#{dimension}").floor
else
size
end
# Handle fill behavior
if @parent && @style.fill &&
(dimension == :width && @parent.is_a?(Flow) ||
dimension == :height && @parent.is_a?(Stack))
return space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow)
return space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack)
# Handle min_width/height and max_width/height
else
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}")
end
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
def background=(_background)
@style.background_canvas.background = (_background)
root.gui_state.request_repaint
@style.background_canvas.background = _background
update_background
end
@@ -251,10 +504,57 @@ module CyberarmEngine
@style.background_canvas.height = height
@style.background_canvas.update
update_background_nine_slice
update_background_image
@style.border_canvas.update
end
def background_nine_slice=(_image_path)
root.gui_state.request_repaint
@style.background_nine_slice_canvas.image = _image_path
update_background_nine_slice
end
def update_background_nine_slice
@style.background_nine_slice_canvas.x = @x
@style.background_nine_slice_canvas.y = @y
@style.background_nine_slice_canvas.z = @z
@style.background_nine_slice_canvas.width = width
@style.background_nine_slice_canvas.height = height
@style.background_nine_slice_canvas.mode = @style.background_nine_slice_mode
@style.background_nine_slice_canvas.color = @style.background_nine_slice_color
@style.background_nine_slice_canvas.left = @style.background_nine_slice_left
@style.background_nine_slice_canvas.top = @style.background_nine_slice_top
@style.background_nine_slice_canvas.right = @style.background_nine_slice_right
@style.background_nine_slice_canvas.bottom = @style.background_nine_slice_bottom
@style.background_nine_slice_canvas.image = @style.background_nine_slice
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
return self if is_root?
@@ -262,11 +562,9 @@ module CyberarmEngine
@root = parent
loop do
if @root.parent.nil?
break
else
@root = @root.parent
end
break unless @root&.parent
@root = @root.parent
end
end
@@ -277,6 +575,12 @@ module CyberarmEngine
@gui_state != nil
end
def focus(_)
warn "#{self.class}#focus was not overridden!"
:handled
end
def recalculate
raise "#{self.class}#recalculate was not overridden!"
end
@@ -295,5 +599,9 @@ module CyberarmEngine
def to_s
"#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}"
end
def inspect
to_s
end
end
end

View File

@@ -1,6 +1,6 @@
module CyberarmEngine
class Element
class Button < Label
class Button < TextBlock
def initialize(text_or_image, options = {}, block = nil)
@image = nil
@scale_x = 1
@@ -10,7 +10,7 @@ module CyberarmEngine
super(text_or_image, options, block)
@style.background_canvas.background = default(:background)
@style.background_canvas.background = @style.background
end
def render
@@ -34,55 +34,15 @@ module CyberarmEngine
@text.draw
end
def enter(_sender)
@focus = false unless window.button_down?(Gosu::MsLeft)
if @focus
@style.background_canvas.background = default(:active, :background)
@text.color = default(:active, :color)
def recalculate
unless @enabled
@style.background_canvas.background = @style.disabled[:background]
@text.color = @style.disabled[:color]
else
@style.background_canvas.background = default(:hover, :background)
@text.color = default(:hover, :color)
@style.background_canvas.background = @style.background
@text.color = @style.color
end
:handled
end
def left_mouse_button(_sender, _x, _y)
@focus = true
@style.background_canvas.background = default(:active, :background)
window.current_state.focus = self
@text.color = default(:active, :color)
:handled
end
def released_left_mouse_button(sender, _x, _y)
enter(sender)
:handled
end
def clicked_left_mouse_button(_sender, _x, _y)
@block.call(self) if @block
:handled
end
def leave(_sender)
@style.background_canvas.background = default(:background)
@text.color = default(:color)
:handled
end
def blur(_sender)
@focus = false
:handled
end
def recalculate
if @image
@width = 0
@height = 0
@@ -104,8 +64,8 @@ module CyberarmEngine
@scale_y = 1
end
@width = _width || @image.width.round * @scale_x
@height = _height || @image.height.round * @scale_y
@width = _width || @image.width.floor * @scale_x
@height = _height || @image.height.floor * @scale_y
update_background
else
@@ -126,9 +86,12 @@ module CyberarmEngine
old_width = width
old_height = height
recalculate
root.gui_state.request_recalculate if old_width != width || old_height != height
if old_width != width || old_height != height
root.gui_state.request_recalculate
else
recalculate
end
publish(:changed, self.value)
end

View File

@@ -5,8 +5,11 @@ module CyberarmEngine
super(options, block)
options[:toggled] = options[:checked]
options[:parent] = self
@toggle_button = ToggleButton.new(options)
@label = Label.new(text, options)
options[:parent] = self
@label = TextBlock.new(text, options)
@label.subscribe(:holding_left_mouse_button) do |sender, x, y|
@toggle_button.left_mouse_button(sender, x, y)

View File

@@ -4,19 +4,30 @@ module CyberarmEngine
include Common
attr_accessor :stroke_color, :fill_color
attr_reader :children, :gui_state, :scroll_x, :scroll_y
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)
@gui_state = options.delete(:gui_state)
super
@scroll_x = 0
@scroll_y = 0
@scroll_speed = 10
@scroll_position = Vector.new(0, 0)
@scroll_speed = 40
@text_color = options[:color]
@children = []
event(:window_size_changed)
end
def build
@@ -31,47 +42,50 @@ module CyberarmEngine
root.gui_state.request_recalculate
end
def remove(element)
root.gui_state.request_recalculate if @children.delete(element)
end
def clear(&block)
@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
$__current_container__ = old_container
CyberarmEngine::Element::Container.current_container = old_container
root.gui_state.request_recalculate
end
def append(&block)
old_container = CyberarmEngine::Element::Container.current_container
CyberarmEngine::Element::Container.current_container = self
block.call(self) if block
CyberarmEngine::Element::Container.current_container = old_container
root.gui_state.request_recalculate
end
def render
Gosu.clip_to(@x, @y, width, height) do
Gosu.clip_to(
@x + @style.border_thickness_left + @style.padding_left,
@y + @style.border_thickness_top + @style.padding_top,
content_width + 1,
content_height + 1
) do
@children.each(&:draw)
end
end
if false # DEBUG
Gosu.flush
def debug_draw
super
Gosu.draw_line(
x, y, Gosu::Color::RED,
x + outer_width, y, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y, Gosu::Color::RED,
x + outer_width, y + outer_height, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y + outer_height, Gosu::Color::RED,
x, y + outer_height, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x, outer_height, Gosu::Color::RED,
x, y, Gosu::Color::RED,
Float::INFINITY
)
@children.each do |child|
child.debug_draw
end
end
@@ -80,6 +94,8 @@ module CyberarmEngine
end
def hit_element?(x, y)
return unless hit?(x, y)
@children.reverse_each do |child|
next unless child.visible?
@@ -98,14 +114,21 @@ module CyberarmEngine
def recalculate
@current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top)
@current_position += @scroll_position
return unless visible?
Stats.increment(:gui_recalculations_last_frame, 1)
stylize
# s = Gosu.milliseconds
layout
old_width = @width
old_height = @height
if is_root?
@width = @style.width = window.width
@height = @style.height = window.height
@@ -116,11 +139,35 @@ module CyberarmEngine
_width = dimensional_size(@style.width, :width)
_height = dimensional_size(@style.height, :height)
@width = _width || (@children.map { |c| c.x + c.outer_width }.max || 0).round
@height = _height || (@children.map { |c| c.y + c.outer_height }.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).floor
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|
child.x += (@x + @style.border_thickness_left) - style.margin_left
child.y += (@y + @style.border_thickness_top) - style.margin_top
@@ -130,9 +177,16 @@ module CyberarmEngine
child.reposition # TODO: Implement top,bottom,left,center, and right positioning
Stats.increment(:gui_recalculations_last_frame, 1)
child.element_visible = child.x >= @x - child.width && child.x <= @x + width &&
child.y >= @y - child.height && child.y <= @y + height
end
# puts "TOOK: #{Gosu.milliseconds - s}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}"
update_background
root.gui_state.request_repaint if @width != old_width || @height != old_height
end
def layout
@@ -140,16 +194,17 @@ module CyberarmEngine
end
def max_width
_width = dimensional_size(@style.width, :width)
if _width
outer_width
else
window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right)
end
# _width = dimensional_size(@style.width, :width)
# if _width
# outer_width
# else
# window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right)
# end
outer_width
end
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 <= window.width
end
@@ -159,7 +214,6 @@ module CyberarmEngine
element.y = element.style.margin_top + @current_position.y
@current_position.x += element.outer_width
@current_position.x = @style.margin_left if @current_position.x >= max_width
end
def tallest_neighbor(querier, _y_position) # Flow
@@ -172,14 +226,14 @@ module CyberarmEngine
response
end
def position_on_next_line(child) # Flow
@current_position.x = @style.margin_left
@current_position.y += tallest_neighbor(child, @current_position.y).outer_height
def position_on_next_line(element) # Flow
@current_position.x = @style.margin_left + @style.padding_left
@current_position.y += tallest_neighbor(element, @current_position.y).outer_height
child.x = child.style.margin_left + @current_position.x
child.y = child.style.margin_top + @current_position.y
element.x = element.style.margin_left + @current_position.x
element.y = element.style.margin_top + @current_position.y
@current_position.x += child.outer_width
@current_position.x += element.outer_width
end
def move_to_next_line(element) # Stack
@@ -189,18 +243,53 @@ module CyberarmEngine
@current_position.y += element.outer_height
end
# def mouse_wheel_up(sender, x, y)
# @children.each {|c| c.y -= @scroll_speed}
# @children.each {|c| c.recalculate}
# end
def mouse_wheel_up(sender, x, y)
return unless @style.scroll
# def mouse_wheel_down(sender, x, y)
# @children.each {|c| c.y += @scroll_speed}
# @children.each {|c| c.recalculate}
# end
if @scroll_position.y < 0
@scroll_position.y += @scroll_speed
@scroll_position.y = 0 if @scroll_position.y > 0
root.gui_state.request_recalculate_for(self)
root.gui_state.request_repaint
return :handled
end
end
def mouse_wheel_down(sender, x, y)
return unless @style.scroll
return unless height < scroll_height
if @scroll_position.y.abs < max_scroll_height
@scroll_position.y -= @scroll_speed
@scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height
root.gui_state.request_recalculate_for(self)
root.gui_state.request_repaint
return :handled
end
end
def scroll_top
@scroll_position.y
end
def scroll_top=(n)
n = 0 if n <= 0
@scroll_position.y = -n
if max_scroll_height.positive?
@scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height
else
@scroll_position.y = 0
end
end
def value
@children.map { |c| c.class }.join(", ")
@children.map(&:class).join(", ")
end
def to_s

View File

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

View File

@@ -1,6 +1,20 @@
module CyberarmEngine
class Element
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)
@filter = options.delete(:filter)
super(text, options, block)
@@ -14,17 +28,11 @@ module CyberarmEngine
@caret_last_interval = Gosu.milliseconds
@show_caret = true
@text_input = Gosu::TextInput.new
@text_input = TextInput.new
@text_input.filter = @filter
@text_input.text = text
@last_text_value = text
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
@last_caret_position = @text_input.caret_pos
@offset_x = 0
@offset_y = 0
@@ -59,6 +67,8 @@ module CyberarmEngine
end
def update
@style_event = :active if @focus
@text.text = if @type == :password
default(:password_character) * @text_input.text.length
else
@@ -67,11 +77,25 @@ module CyberarmEngine
if @last_text_value != value
@last_text_value = value
@show_caret = true
@caret_last_interval = Gosu.milliseconds
root.gui_state.request_repaint
publish(:changed, value)
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
root.gui_state.request_repaint
@caret_last_interval = Gosu.milliseconds
@show_caret = !@show_caret
@@ -94,20 +118,20 @@ module CyberarmEngine
@text_input.caret_pos = @text_input.text.length
when Gosu::KB_C
if @text_input.selection_start < @text_input.caret_pos
Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos])
else
Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start])
end
Gosu.clipboard = if @text_input.selection_start < @text_input.caret_pos
@text_input.text[@text_input.selection_start...@text_input.caret_pos]
else
@text_input.text[@text_input.caret_pos...@text_input.selection_start]
end
when Gosu::KB_X
chars = @text_input.text.chars
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)
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)
end
@@ -115,10 +139,9 @@ module CyberarmEngine
when Gosu::KB_V
if instance_of?(EditLine) # EditLine assumes a single line of text
@text_input.text = @text_input.text.insert(@text_input.caret_pos,
Clipboard.paste.encode("UTF-8").gsub("\n", ""))
@text_input.insert_text(Gosu.clipboard.gsub("\n", ""))
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
@@ -176,7 +199,7 @@ module CyberarmEngine
if @type == :password
@text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length)
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
@@ -192,28 +215,42 @@ module CyberarmEngine
:handled
end
def enter(_sender)
if @focus
@style.background_canvas.background = default(:active, :background)
@text.color = default(:active, :color)
def focus(sender)
@focus = true
window.text_input = @text_input
@text_input.caret_pos = @text_input.selection_start = @text_input.text.length
update_styles(:active)
:handled
end
def enter(sender)
if @enabled && @focus
update_styles(:active)
elsif @enabled && !@focus
update_styles(:hover)
else
@style.background_canvas.background = default(:hover, :background)
@text.color = default(:hover, :color)
update_styles(:disabled)
end
:handled
end
def leave(sender)
super unless @focus
if @enabled && @focus
update_styles(:active)
elsif @enabled && !@focus
update_styles
else
update_styles(:disabled)
end
:handled
end
def blur(_sender)
@focus = false
@style.background_canvas.background = default(:background)
@text.color = default(:color)
super
window.text_input = nil
:handled
@@ -251,6 +288,10 @@ module CyberarmEngine
def value
@text_input.text
end
def value=(string)
@text_input.text = string
end
end
end
end

View File

@@ -1,11 +1,13 @@
module CyberarmEngine
class Element
class Image < Element
def initialize(path, options = {}, block = nil)
def initialize(path_or_image, options = {}, block = nil)
super(options, block)
@path = path
@path = path_or_image if path_or_image.is_a?(String)
@image = Gosu::Image.new(path_or_image, retro: @options[:retro], tileable: @options[:tileable]) if @path
@image = path_or_image unless @path
@image = Gosu::Image.new(path, retro: @options[:image_retro])
@scale_x = 1
@scale_y = 1
end
@@ -43,11 +45,26 @@ module CyberarmEngine
@scale_y = 1
end
@width = _width || @image.width.round * @scale_x
@height = _height || @image.height.round * @scale_y
@width = _width || (@image.width * @scale_x).floor
@height = _height || (@image.height * @scale_y).floor
update_background
end
def value
@image
end
def value=(path_or_image, retro: false, tileable: false)
@path = path_or_image if path_or_image.is_a?(String)
@image = Gosu::Image.new(path_or_image, retro: retro, tileable: tileable) if @path
@image = path_or_image unless @path
recalculate
end
def path
@path
end
end

View File

@@ -1,131 +0,0 @@
module CyberarmEngine
class Element
class Label < Element
def initialize(text, options = {}, block = nil)
super(options, block)
@text = Text.new(
text, font: @options[:font], z: @z, color: @options[:color],
size: @options[:text_size], shadow: @options[:text_shadow],
shadow_size: @options[:text_shadow_size],
shadow_color: @options[:text_shadow_color]
)
@raw_text = text
end
def render
@text.draw
end
def clicked_left_mouse_button(_sender, _x, _y)
@block&.call(self)
# return :handled
end
def recalculate
@width = 0
@height = 0
_width = dimensional_size(@style.width, :width)
_height = dimensional_size(@style.height, :height)
handle_text_wrapping(_width)
@width = _width || @text.width.round
@height = _height || @text.height.round
@text.y = @style.border_thickness_top + @style.padding_top + @y
@text.z = @z + 3
if (text_alignment = @options[:text_align])
case text_alignment
when :left
@text.x = @style.border_thickness_left + @style.padding_left + @x
when :center
@text.x = if @text.width <= outer_width
@x + outer_width / 2 - @text.width / 2
else # Act as left aligned
@style.border_thickness_left + @style.padding_left + @x
end
when :right
@text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right)
end
end
update_background
end
def handle_text_wrapping(max_width)
max_width ||= @parent&.width
max_width ||= @x - (window.width + noncontent_width)
wrap_behavior = style.text_wrap
copy = @raw_text.to_s.dup
if max_width >= line_width(copy[0]) && line_width(copy) > max_width && wrap_behavior != :none
breaks = []
line_start = 0
line_end = copy.length
while line_start != copy.length
if line_width(copy[line_start...line_end]) > max_width
line_end = ((line_end - line_start) / 2.0)
elsif line_end < copy.length && line_width(copy[line_start...line_end + 1]) < max_width
# To small, grow!
# TODO: find a more efficient way
line_end += 1
else # FOUND IT!
entering_line_end = line_end.floor
max_reach = line_end.floor - line_start < 63 ? line_end.floor - line_start : 63
reach = 0
if wrap_behavior == :word_wrap
max_reach.times do |i|
reach = i
break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
end
puts "Max width: #{max_width}/#{line_width(@raw_text)} Reach: {#{reach}/#{max_reach}} Line Start: #{line_start}/#{line_end.floor} (#{copy.length}|#{@raw_text.length}) [#{entering_line_end}] '#{copy}' {#{copy[line_start...line_end]}}"
line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation
end
breaks << line_end.floor
line_start = line_end.floor
line_end = copy.length
break if entering_line_end == copy.length || reach == max_reach
end
end
breaks.each_with_index do |pos, index|
copy.insert(pos + index, "\n") if pos + index >= 0 && pos + index < copy.length
end
end
@text.text = copy
end
def line_width(text)
(@x + @text.textobject.markup_width(text) + noncontent_width)
end
def value
@raw_text
end
def value=(value)
@raw_text = value.to_s.chomp
old_width = width
old_height = height
recalculate
root.gui_state.request_recalculate if old_width != width || old_height != height
publish(:changed, self.value)
end
end
end
end

View File

@@ -13,22 +13,34 @@ module CyberarmEngine
@style.background_canvas.background = default(:background)
# 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
@x = @__list_box.x
@y = @__list_box.y + @__list_box.height
@y = @__list_box.y - height if @y + height > window.height
end
@menu.instance_variable_set(:"@__list_box", self)
def @menu.recalculate
super
recalculate_menu
end
self.choose = @choose
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)
valid = @items.detect { |i| i == item }
return unless valid # TODO: Throw an error?
raise "Invalid value '#{item}' for choose, valid options were: #{@items.map { |i| "#{i.inspect}" }.join(", ")}" unless valid
@choose = item
@@ -43,15 +55,36 @@ module CyberarmEngine
:handled
end
def clicked_left_mouse_button(_sender, _x, _y)
# @block&.call(self.value) if @enabled
:handled
end
def show_menu
@menu.clear
@items.each do |item|
[@block]
block = proc { self.choose = item; @block.call(item) if @block }
b = Button.new(item,
{ parent: @menu, width: 1.0, theme: @options[:theme], margin: 0, border_color: 0x00ffffff }, block)
@menu.add(b)
@menu.style.width = width
@items.each do |item|
next if item == self.value
btn = Button.new(
item,
{
parent: @menu,
width: 1.0,
theme: @options[:theme],
margin: 0,
border_color: 0x00ffffff
},
proc do
self.choose = item
@block&.call(self.value)
end
)
@menu.add(btn)
end
recalculate

View File

@@ -1,9 +1,16 @@
module CyberarmEngine
class Element
class Progress < Element
attr_reader :type
def initialize(options = {}, block = nil)
super(options, block)
@animation_speed = options[:animation_speed] || 3_000
@marquee_width = options[:marquee_width] || 0.25
@marquee_offset = 0
@marquee_animation_time = Gosu.milliseconds
@type = options[:type] || :linear
@fraction_background = Background.new(background: @style.fraction_background)
self.value = options[:fraction] || 0.0
end
@@ -24,15 +31,46 @@ module CyberarmEngine
def update_background
super
@fraction_background.x = @style.border_thickness_left + @style.padding_left + @x
@fraction_background.x = (@style.border_thickness_left + @style.padding_left + @x) + @marquee_offset
@fraction_background.y = @style.border_thickness_top + @style.padding_top + @y
@fraction_background.z = @z
@fraction_background.width = @width * @fraction
@fraction_background.width = @width * (@type == :marquee ? @marquee_width : @fraction)
@fraction_background.height = @height
@fraction_background.background = @style.fraction_background
end
def update
super
return unless @type == :marquee
marquee_width = @width * @marquee_width
range = @width + marquee_width
@marquee_offset = (@width * (Gosu.milliseconds - @marquee_animation_time) / @animation_speed) - marquee_width
@marquee_animation_time = Gosu.milliseconds if @marquee_offset > range
update_background
root.gui_state.request_repaint
end
def type=(type)
@type = type
case type
when :linear
@marquee_offset = 0
when :marquee
@marquee_offset = 0
@marquee_animation_time = Gosu.milliseconds
else
raise ArgumentError, "Only types :linear and :marquee are supported"
end
update_background
end
def value
@fraction
end
@@ -40,9 +78,13 @@ module CyberarmEngine
def value=(decimal)
raise "value must be number" unless decimal.is_a?(Numeric)
old_value = @fraction
@fraction = decimal.clamp(0.0, 1.0)
update_background
root.gui_state.request_repaint if @fraction != old_value
publish(:changed, @fraction)
@fraction
end

View File

@@ -42,7 +42,7 @@ module CyberarmEngine
@step_size = @options[:step] || 0.1
@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)
end
@@ -61,10 +61,10 @@ module CyberarmEngine
end
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)
@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
def draw
@@ -97,6 +97,8 @@ module CyberarmEngine
position_handle
@handle.recalculate
root.gui_state.request_repaint
publish(:changed, @value)
end
end

View File

@@ -0,0 +1,212 @@
module CyberarmEngine
class Element
class TextBlock < Element
def initialize(text, options = {}, block = nil)
super(options, block)
@text = Text.new(
text, font: @options[:font], z: @z, color: @options[:color],
size: @options[:text_size], shadow: @options[:text_shadow],
static: @options[:text_static],
shadow_size: @options[:text_shadow_size],
shadow_color: @options[:text_shadow_color],
border: @options[:text_border],
border_size: @options[:text_border_size],
border_color: @options[:text_border_color]
)
@raw_text = text
end
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
end
else
@text.draw
end
end
def recalculate
unless @enabled
@text.color = @style.disabled[:color]
else
@text.color = @style.color
end
old_width = @width
old_height = @height
@width = 0
@height = 0
_width = dimensional_size(@style.width, :width)
_height = dimensional_size(@style.height, :height)
handle_text_wrapping(_width)
@width = _width || @text.width.floor
@height = _height || @text.height.floor
@text.y = @style.border_thickness_top + @style.padding_top + @y
@text.z = @z + 3
if (text_alignment = @options[:text_align] || @options[:text_h_align])
case text_alignment
when :left
@text.x = @style.border_thickness_left + @style.padding_left + @x
when :center
@text.x = if @text.width <= width
@x + width / 2 - @text.width / 2
else # Act as left aligned
@style.border_thickness_left + @style.padding_left + @x
end
when :right
@text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right)
end
end
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
root.gui_state.request_repaint if @width != old_width || @height != old_height
end
def handle_text_wrapping(max_width)
max_width ||= @parent&.content_width
max_width ||= @x - (window.width + noncontent_width)
wrap_behavior = style.text_wrap
copy = @raw_text.to_s.dup
# Only perform text wrapping: if it is enabled, is possible to wrap, and text is too long to fit on one line
if wrap_behavior != :none && line_width(copy[0]) <= max_width && line_width(copy) > max_width
breaks = [] # list of indexes to insert a line break
line_start = 0
line_end = copy.length
stalled = false
stalled_interations = 0
max_stalled_iterations = 10
checked_copy_length = line_width(copy[line_start..line_end])
# find length of lines
while line_width(copy[line_start..line_end]) > max_width && stalled_interations < max_stalled_iterations
search_start = line_start
search_end = line_end
# Perform a binary search to find length of line
while search_start < search_end
midpoint = ((search_start.to_f + search_end) / 2.0).floor
if line_width(copy[line_start..midpoint]) > max_width
search_end = midpoint
else
search_start = midpoint + 1
end
end
if wrap_behavior == :word_wrap
word_search_end = search_end
failed = false
until(copy[word_search_end].to_s.match(/[[:punct:]]| /))
word_search_end -= 1
if word_search_end <= 1 || word_search_end < line_start
failed = true
break
end
end
line_start = failed ? search_end : word_search_end + 1 # walk in front of punctuation
else
line_start = search_end
end
breaks << line_start
# Prevent locking up due to outer while loop text width < max_width check not being satisfied.
stalled = checked_copy_length == line_width(copy[line_start..line_end])
checked_copy_length = line_width(copy[line_start..line_end])
stalled_interations += 1 if stalled
stalled_interations = 0 unless stalled
end
breaks.each_with_index do |pos, index|
copy.insert(pos + index, "\n") if pos + index >= 0 && pos + index < copy.length
end
end
@text.text = copy
end
def line_width(text)
(@text.textobject.markup_width(text) + noncontent_width)
end
def value
@raw_text
end
def value=(value)
old_value = @raw_text
@raw_text = value.to_s.chomp
old_width = width
old_height = height
if old_width != width || old_height != height
root.gui_state.request_recalculate
else
recalculate
end
root.gui_state.request_repaint if old_value != @raw_text
publish(:changed, self.value)
end
end
class Banner < TextBlock
end
class Title < TextBlock
end
class Subtitle < TextBlock
end
class Tagline < TextBlock
end
class Caption < TextBlock
end
class Para < TextBlock
end
class Inscription < TextBlock
end
class ToolTip < TextBlock
end
class Link < TextBlock
end
end
end

View File

@@ -5,7 +5,7 @@ module CyberarmEngine
def initialize(options, block = nil)
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)
@_image = @image

View File

@@ -15,12 +15,19 @@ module CyberarmEngine
return unless enabled?
return :handled if respond_to?(event) && (send(event, self, *args) == :handled)
was_handled = false
was_handled = true if respond_to?(event) && (send(event, self, *args) == :handled)
@event_handler[event].reverse_each do |handler|
return :handled if handler.call(self, *args) == :handled
if handler.call(self, *args) == :handled
was_handled = true
break
end
end
return :handled if was_handled
parent.publish(event, *args) if parent
nil
end

View File

@@ -12,7 +12,7 @@ module CyberarmEngine
@root_container = Element::Stack.new(gui_state: self)
@game_objects << @root_container
$__current_container__ = @root_container
CyberarmEngine::Element::Container.current_container = @root_container
@active_width = window.width
@active_height = window.height
@@ -25,13 +25,19 @@ module CyberarmEngine
@last_mouse_pos = nil
@dragging_element = nil
@pending_recalculate_request = false
@pending_element_recalculate_requests = []
@needs_repaint = false
@tip = CyberarmEngine::Text.new("", size: 22, z: Float::INFINITY)
@menu = nil
@min_drag_distance = 0
@mouse_pos = Vector.new
end
def post_setup
@tip = Element::ToolTip.new("", parent: @root_container, z: Float::INFINITY, theme: current_theme)
end
# throws :blur event to focused element and sets GuiState focused element
# Does NOT throw :focus event at element or set element as focused
def focus=(element)
@@ -51,11 +57,23 @@ module CyberarmEngine
@menu.draw
end
if @tip.text.length.positive?
if @tip && @tip.value.length.positive?
Gosu.flush
Gosu.draw_rect(@tip.x - 2, @tip.y - 2, @tip.width + 4, @tip.height + 4, 0xff020202, Float::INFINITY)
@tip.draw
end
if defined?(GUI_DEBUG)
Gosu.flush
@root_container.debug_draw
end
@needs_repaint = false
end
def needs_repaint?
@needs_repaint
end
def update
@@ -67,9 +85,31 @@ module CyberarmEngine
@pending_recalculate_request = false
end
@pending_element_recalculate_requests.each(&:recalculate)
@pending_element_recalculate_requests.clear
if @pending_focus_request
@pending_focus_request = false
self.focus = @pending_focus_element
@pending_focus_element.publish(:focus)
end
@menu&.update
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 ||= @root_container.hit_element?(window.mouse_x, window.mouse_y)
@@ -87,11 +127,17 @@ module CyberarmEngine
if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos
if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay
@tip.text = @mouse_over.tip if @mouse_over
@tip.x = window.mouse_x - @tip.width / 2
@tip.y = window.mouse_y - @tip.height - 4
@tip.value = @mouse_over.tip if @mouse_over
@tip.x = window.mouse_x
@tip.x = 0 if @tip.x < 0
@tip.x = window.width - @tip.width if @tip.x + @tip.width > window.width
@tip.y = window.mouse_y - (@tip.height + 5)
@tip.y = 0 if @tip.y < 0
@tip.y = window.height - @tip.height if @tip.y + @tip.height > window.height
@tip.update
@tip.recalculate
else
@tip.text = ""
@tip.value = ""
end
else
@mouse_moved_at = Gosu.milliseconds
@@ -99,15 +145,6 @@ module CyberarmEngine
@last_mouse_pos = Vector.new(window.mouse_x, window.mouse_y)
@mouse_pos = @last_mouse_pos.clone
request_recalculate if @active_width != window.width || @active_height != window.height
@active_width = window.width
@active_height = window.height
end
def tool_tip_delay
500 # ms
end
def button_down(id)
@@ -144,6 +181,13 @@ module CyberarmEngine
end
@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
def redirect_mouse_button(button)
@@ -154,7 +198,7 @@ module CyberarmEngine
@focus = nil
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_on[button] = @mouse_over
@@ -168,7 +212,7 @@ module CyberarmEngine
def redirect_released_mouse_button(button)
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)
if @mouse_over == @mouse_down_on[button]
@mouse_over.publish(:"clicked_#{button}_mouse_button", window.mouse_x,
@@ -207,12 +251,42 @@ module CyberarmEngine
@pending_recalculate_request = true
end
def request_recalculate_for(element)
# element is already queued
return if @pending_element_recalculate_requests.detect { |e| e == element }
@pending_element_recalculate_requests << element
end
def request_focus(element)
@pending_focus_request = true
@pending_focus_element = element
end
def request_repaint
# puts caller[0..4]
# puts
@needs_repaint = true
end
def show_menu(list_box)
@menu = list_box
end
def hide_menu
return unless @menu
@hid_menu_for = @menu.parent
@menu = nil
end
def to_s
# "#{self.class} children=#{@children.map { |c| c.to_s }}"
@root_container.to_s
end
def inspect
to_s
end
end
end

View File

@@ -17,8 +17,20 @@ end
module CyberarmEngine
class Style
attr_reader :hash
def initialize(hash = {})
@hash = Marshal.load(Marshal.dump(hash))
h = Marshal.load(Marshal.dump(hash))
h[:default] = {}
h.each do |key, value|
next if value.is_a?(Hash)
h[:default][key] = value
end
@hash = h
end
def method_missing(method, *args)
@@ -27,9 +39,8 @@ module CyberarmEngine
@hash[method.to_s.sub("=", "").to_sym] = args.first
elsif args.size == 0
elsif args.empty?
@hash[method]
else
raise ArgumentError, "Did not expect arguments"
end

View File

@@ -64,6 +64,10 @@ module CyberarmEngine
border_radius: 0
},
Container: { # < Element (Base class for Stack and Flow)
debug_color: Gosu::Color::YELLOW
},
Button: { # < Label
margin: 1,
padding: 4,
@@ -72,6 +76,7 @@ module CyberarmEngine
border_radius: 0,
background: ["ffc75e61".to_i(16), "ffe26623".to_i(16)],
text_align: :center,
text_v_align: :center,
text_wrap: :none,
hover: {
@@ -82,6 +87,11 @@ module CyberarmEngine
active: {
color: Gosu::Color::BLACK,
background: ["ffB23E41".to_i(16)]
},
disabled: {
color: Gosu::Color::GRAY,
background: 0xff303030
}
},
@@ -93,28 +103,95 @@ module CyberarmEngine
caret_color: Gosu::Color::WHITE,
caret_interval: 500,
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
color: Gosu::Color::WHITE,
tileable: false,
retro: false
},
Label: { # < Element
TextBlock: { # < Element
text_size: 28,
text_wrap: :none, # :word_wrap, :break_word, :none
text_wrap: :word_wrap, # :word_wrap, :break_word, :none
text_shadow: false,
text_border: false,
text_align: :left,
font: "Arial",
margin: 0,
padding: 2
padding: 2,
disabled: {
color: Gosu::Color.rgb(175, 175, 175),
}
},
Banner: { # < TextBlock
text_size: 48
},
Title: { # < TextBlock
text_size: 34
},
Subtitle: { # < TextBlock
text_size: 26
},
Tagline: { # < TextBlock
text_size: 24
},
Caption: { # < TextBlock
text_size: 22
},
Para: { # < TextBlock
text_size: 18
},
Inscription: { # < TextBlock
text_size: 16
},
ToolTip: { # < TextBlock
delay: 100, # ms
color: Gosu::Color::WHITE,
padding_top: 4,
padding_bottom: 4,
padding_left: 8,
padding_right: 8,
border_thickness: 1,
border_color: 0xffaaaaaa,
background: 0xff404040
},
Link: { # < TextBlock
color: Gosu::Color::BLUE,
border_thickness: 1,
border_bottom_color: Gosu::Color::BLUE,
hover: {
color: 0xff_ff00ff,
border_bottom_color: 0xff_ff00ff
},
active: {
color: 0xff_ff0000,
border_bottom_color: 0xff_ff0000
}
},
ToggleButton: { # < Button
checkmark: ""
},
CheckBox: { # < Flow
text_wrap: :none
},
Progress: { # < Element
width: 250,
height: 36,

View File

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

View File

@@ -1,44 +1,60 @@
module CyberarmEngine
class Window < Gosu::Window
include Common
IMAGES = {}
SAMPLES = {}
SONGS = {}
attr_accessor :show_cursor
attr_writer :exit_on_opengl_error
attr_reader :last_frame_time
attr_reader :last_frame_time, :states
def self.now
Gosu.milliseconds
end
def self.dt
$window.last_frame_time / 1000.0
instance.last_frame_time / 1000.0
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
def initialize(width: 800, height: 600, fullscreen: false, update_interval: 1000.0 / 60, resizable: false, borderless: false)
@show_cursor = false
@has_focus = false
super(width, height, fullscreen: fullscreen, update_interval: update_interval, resizable: resizable, borderless: borderless)
$window = self
Window.instance = self
@last_frame_time = Gosu.milliseconds - 1
@current_frame_time = Gosu.milliseconds
self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.language}"
@states = []
@exit_on_opengl_error = false
preload_default_shaders if respond_to?(:preload_default_shaders)
setup if defined?(setup)
end
def draw
current_state.draw if current_state
current_state&.draw
end
def update
Stats.clear
current_state.update if current_state
current_state&.update
@last_frame_time = Gosu.milliseconds - @current_frame_time
@current_frame_time = Gosu.milliseconds
end
@@ -47,6 +63,48 @@ module CyberarmEngine
@show_cursor
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
@last_frame_time / 1000.0
end
@@ -59,26 +117,18 @@ module CyberarmEngine
@exit_on_opengl_error
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 = {})
options = { setup: true }.merge(options)
if klass.instance_of?(klass.class) && defined?(klass.options)
@states << klass
klass.setup if options[:setup]
klass.post_setup if options[:setup]
else
@states << klass.new(options) if child_of?(klass, GameState)
@states << klass.new if child_of?(klass, Element::Container)
current_state.setup if current_state.instance_of?(klass) && options[:setup]
current_state.post_setup if current_state.instance_of?(klass) && options[:setup]
end
end
@@ -90,14 +140,20 @@ module CyberarmEngine
@states.last
end
def previous_state
if @states.size > 1 && state = @states[@states.size - 2]
state
end
end
def pop_state
@states.pop
current_state.request_repaint if current_state&.is_a?(GuiState)
end
def shift_state
@states.shift
current_state.request_repaint if current_state&.is_a?(GuiState)
end
def has_focus?
@has_focus
end
# Sourced from https://gist.github.com/ippa/662583