Last active
May 31, 2024 03:17
-
-
Save TheEyesightDim/c98fa7035d1309670771911f2ff5c120 to your computer and use it in GitHub Desktop.
Main code for Orthopaint: https://orthohedron.itch.io/orthopaint
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "app/strings.rb" | |
def undo_last_stroke(args) | |
args.state.counts.pop | |
stroke = args.state.counts.pop | |
args.state.commands.slice!(0 - stroke, stroke) if stroke | |
end | |
def tick(args) | |
args.state.help_layer ||= :shown | |
# Bind inputs to new names for easier access. | |
mousewheel = args.inputs.mouse.wheel ? args.inputs.mouse.wheel.y : 0 | |
mx = args.inputs.mouse.x | |
my = args.inputs.mouse.y | |
key_i = args.inputs.keyboard.key_down.i | |
key_u = args.inputs.keyboard.key_down.u | |
key_h = args.inputs.keyboard.h | |
key_s = args.inputs.keyboard.s | |
key_l = args.inputs.keyboard.l | |
key_a = args.inputs.keyboard.a | |
args.state.click = :up | |
args.state.click = :down if args.inputs.mouse.button_left | |
# Draw the bg first. This is the base layer and it always exists. | |
args.outputs.primitives << { | |
x: 0, | |
y: 0, | |
w: 1280, | |
h: 720, | |
r: 32, | |
g: 32, | |
b: 32, | |
}.solid | |
# Our canvas for painting It should be erased on a reset. | |
# EDIT: Now I'm using a command list because I don't know how to hold the texture otherwise. | |
args.state.commands ||= [] | |
# Array that holds frame counts for brush strokes | |
# used for undo. | |
args.state.counts ||= [0] | |
# we need to keep updating the last value of `counts` if it matches our current state, | |
# or else push a new value to start a new 'stroke.' | |
if args.state.click == :down | |
args.state.counts[-1] += 1 | |
else | |
args.state.counts << 0 unless args.state.counts[-1] == 0 | |
end | |
# Set brush size default. | |
brush = args.state.brush_size ||= 8 | |
# Modify the brush size based on mousewheel movement. | |
args.state.brush_size = (args.state.brush_size + (mousewheel * 4)).clamp(4, 256) | |
# Make a hash for a rect dims to reuse later. | |
rect = { x: (mx - brush * 0.5), y: (my - brush * 0.5), w: brush, h: brush } | |
####### NOTES ABOUT COLORS ####### | |
# 2 color formats: RGBA (for DR) and HSLA (for the interface) | |
# RGBA is all in 0..255 | |
# HSLA is [360 deg, 0..1, 0..1, 0..255] | |
# Set the default color. | |
# Only the ones in `args.state` get saved between ticks. | |
args.state.hsl_color ||= { h: rand(360), s: 0.999, l: 0.5, a: 255 } | |
args.state.rgb_color ||= hsl_to_rgb(args.state.hsl_color) | |
# Now modify the color based on inputs. Be sure to modify the ones held in `args.state`. | |
if key_h || key_s || key_l || key_a | |
shifts = [key_h, key_s, key_l, key_a].map { |k| if k then 1 else 0 end } | |
args.state.hsl_color = shift_hsla(args.state.hsl_color, *shifts) | |
args.state.rgb_color = hsl_to_rgb(args.state.hsl_color) | |
end | |
undo_last_stroke(args) if key_u | |
# Painting happens here. | |
# First, draw the solid to the render target... | |
color = args.state.rgb_color | |
if args.state.click == :down | |
args.state.commands << rect.merge(color) | |
end | |
# Then draw the render target to main output. | |
args.state.commands.each do |comm| | |
args.outputs.primitives << comm.solid | |
end | |
# Done painting. | |
# Draw the selection border. Note the last merge: it should never be transparent. | |
args.outputs.primitives << rect.merge(color).merge({ a: 255 }).border | |
# Draw color preview square in the bottom-left corner. | |
# First, draw the background... | |
args.outputs.primitives << { x: 30, y: 30, w: 180, h: 100, a: 168 }.solid | |
# Then, draw the active color. | |
args.outputs.primitives << { x: 35, y: 35, w: 70, h: 90 }.merge(color).solid | |
# Now print the HSL values all neatly. | |
hsl = args.state.hsl_color | |
args.outputs.labels << { text: "H:", x: 110, y: 120, size_enum: -2 }.merge($lb_common) | |
args.outputs.labels << { text: "S:", x: 110, y: 100, size_enum: -2 }.merge($lb_common) | |
args.outputs.labels << { text: "L:", x: 110, y: 80, size_enum: -2 }.merge($lb_common) | |
args.outputs.labels << { text: "A:", x: 110, y: 60, size_enum: -2 }.merge($lb_common) | |
args.outputs.labels << { text: "#{"%.3f" % hsl[:h]}", x: 140, y: 125 }.merge($lb_common) | |
args.outputs.labels << { text: "#{"%.3f" % hsl[:s]}", x: 140, y: 105 }.merge($lb_common) | |
args.outputs.labels << { text: "#{"%.3f" % hsl[:l]}", x: 140, y: 85 }.merge($lb_common) | |
args.outputs.labels << { text: "#{hsl[:a]}", x: 140, y: 65 }.merge($lb_common) | |
# Toggle help text. | |
if key_i | |
if args.state.help_layer == :shown | |
args.state.help_layer = :hidden | |
else | |
args.state.help_layer = :shown | |
end | |
end | |
if args.state.help_layer == :shown | |
print_help_text args | |
end | |
# Reset button. | |
if args.inputs.keyboard.key_down.enter | |
args.gtk.reset Time.now.to_i | |
end | |
end | |
def shift_hsla(color_hash, h_shift, s_shift, l_shift, a_shift) | |
{ | |
h: (color_hash[:h] + h_shift) % 360, | |
s: fractional(color_hash[:s] + s_shift * 0.0101), | |
l: fractional(color_hash[:l] + l_shift * 0.0101), | |
a: (color_hash[:a] + a_shift) % 256, | |
} | |
end | |
def rgb_to_hsl(color_hash) | |
r, g, b = [color_hash[:r], color_hash[:g], color_hash[:b]].map { |c| (c / 255.0) } | |
max = [r, g, b].max | |
min = [r, g, b].min | |
delta = (max - min).to_f | |
f = 1 - (max + max - delta - 1).abs | |
h = 0 | |
if delta != 0 | |
case max | |
when r | |
h = (g - b) / delta | |
when g | |
h = 2 + (b - r) / delta | |
when b | |
h = 4 + (r - g) / delta | |
end | |
else | |
h = 0 | |
end | |
h = 60 * (h < 0 ? h + 6 : h) | |
s = f != 0 ? delta / f : 0 | |
l = (max + max - min) / 2 | |
{ h: h, s: s, l: l, a: color_hash[:a] } | |
end | |
def hsl_to_rgb(color_hash) | |
h, s, l = [color_hash[:h], color_hash[:s], color_hash[:l]] | |
c = s * [l, 1 - l].min | |
fn = ->(n, k = (n + h / 30.0) % 12) { l - c * [[k - 3, 9 - k, 1].min, -1].max } | |
{ r: fn.(0) * 255, g: fn.(8) * 255, b: fn.(4) * 255, a: color_hash[:a] } | |
end | |
def fractional(num) | |
num - num.to_i | |
end | |
def print_help_text(args) | |
# Print labels against a translucent bg. | |
args.outputs.primitives << { x: 0, y: 600, w: 1280, h: 600, a: 168 }.solid | |
# Print help text. | |
args.outputs.labels << $lb_hash[:help_splash_1].merge($lb_common) | |
args.outputs.labels << $lb_hash[:help_splash_2].merge($lb_common) | |
args.outputs.labels << $lb_hash[:help_splash_3].merge($lb_common) | |
# Other stuff, like perf. | |
# Calculating frame time average over last ten frames... | |
args.state.ftimes ||= [Time.now.to_f] | |
args.state.ftimes << Time.now.to_f | |
args.state.ftimes.slice!(0..-10) | |
buf = [] | |
args.state.ftimes.each_cons(2) { |a| buf << (a[-1] - a[0]) } | |
avg = (buf.reduce(0) { |m, x| m + x } / buf.length) * 1000 | |
args.outputs.labels << { text: "ms/F (avg. of 10): #{"%2.2f" % avg}", x: 1080, y: 690 }.merge($lb_common) | |
# Mouse coords | |
x, y = args.inputs.mouse.x, args.inputs.mouse.y | |
args.outputs.labels << { text: "x: #{x} | y: #{y}", x: 1080, y: 660 }.merge($lb_common) | |
#button | |
args.outputs.labels << { text: "#{args.state.click}", x: 1080, y: 630 }.merge($lb_common) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment