From f31790189ecebbf281dab3c42b5d8dd9affb0e5d Mon Sep 17 00:00:00 2001 From: Ronja Date: Thu, 25 Nov 2021 17:57:45 +0100 Subject: [PATCH] rgb + hsv sliders --- colorpicker.wren | 485 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 334 insertions(+), 151 deletions(-) diff --git a/colorpicker.wren b/colorpicker.wren index fd4ece2..a2d46ca 100644 --- a/colorpicker.wren +++ b/colorpicker.wren @@ -1,24 +1,127 @@ -import "luxe: world" for Entity, UIClear, UILayoutMode, UIBehave, UIContain, Assets, Material, UI, UIEvent -import "luxe: ui" for Control, UIList, UIWindow, UIButton, UIPanel, UIImage +import "luxe: world" for Entity, UIClear, UILayoutMode, UIBehave, UIContain, Assets, Material, UI, UIEvent, UILayoutBehave +import "luxe: ui" for Control, UIList, UIWindow, UIButton, UIPanel, UIImage, UISlider, UILabel +import "luxe: ui/field/number" for UINumber import "luxe: id" for ID import "luxe: draw" for PathStyle import "luxe: math" for Math import "luxe: color" for Color +class ColorPickerData { + + //parameters - we could make some of this dynamic if we wanted to + triangle_size {35} + outer_ring_size {200} + inner_ring_size {140} + + r{_r} + g{_g} + b{_b} + + h{_h} + s{_s} + v{_v} + + a{_a} + + rgba{[_r, _g, _b, _a]} + hsva{[_h, _s, _v, _a]} + + toString{"Data - HSV:[%(h), %(s), %(v)] - RGB:[%(r), %(g), %(b)] - A:[%(a)]"} + + construct new(){ + set_rgba(Color.hex(0xFFAABB)) + } + + set_rgba(col){set_rgba(col, true)} + set_rgba(col, update_spaces){ + System.print("Set RGB!") + if(!ColorPicker.approx(_r, col.r)) _r = col.r + if(!ColorPicker.approx(_g, col.g)) _g = col.g + if(!ColorPicker.approx(_b, col.b)) _b = col.b + if(!ColorPicker.approx(_a, col.a)) _a = col.a + + if(!update_spaces) return + + var hsv = Color.rgb2hsv(col) + //be careful not to destroy hue when doing rgb to hsv + if(!ColorPicker.approx(_h, hsv.x) && !ColorPicker.approx(_s, 0)) _h = hsv.x + if(!ColorPicker.approx(_s, hsv.y)) _s = hsv.y + if(!ColorPicker.approx(_v, hsv.z)) _v = hsv.z + } + + set_hsva(col){set_hsva(col, true)} + set_hsva(col, update_spaces){ + System.print("Set HSV!") + if(!ColorPicker.approx(_h, col.x)) _h = col.x + if(!ColorPicker.approx(_s, col.y)) _s = col.y + if(!ColorPicker.approx(_v, col.z)) _v = col.z + if(!ColorPicker.approx(_a, col.a)) _a = col.a + + if(!update_spaces) return + + var rgb = Color.hsv2rgb(col) + if(!ColorPicker.approx(_r, rgb.r)) _r = rgb.r + if(!ColorPicker.approx(_g, rgb.g)) _g = rgb.g + if(!ColorPicker.approx(_b, rgb.b)) _b = rgb.b + } + + set_rgba_component(index: Num, value: Num){ + if(index == 0){ + set_rgba([value, _g, _b, _a]) + } else if(index == 1){ + set_rgba([_r, value, _b, _a]) + } else if(index == 2){ + set_rgba([_r, _g, value, _a]) + } else if(index == 3){ + set_rgba([_r, _g, _b, value]) + } + } + + get_rgba_component(index: Num){ + if(index == 0){ + return _r + } else if(index == 1){ + return _g + } else if(index == 2){ + return _b + } else if(index == 3){ + return _a + } + } + + set_hsva_component(index: Num, value: Num){ + if(index == 0){ + set_hsva([value, _s, _v, _a]) + } else if(index == 1){ + set_hsva([_h, value, _v, _a]) + } else if(index == 2){ + set_hsva([_h, _s, value, _a]) + } else if(index == 3){ + set_hsva([_h, _s, _v, value]) + } + } + + get_hsva_component(index: Num){ + if(index == 0){ + return _h + } else if(index == 1){ + return _s + } else if(index == 2){ + return _v + } else if(index == 3){ + return _a + } + } + +} + class ColorPicker{ static create(ui: Entity) : Control{ - //parameters - we could expose some of this if we wanted to - var base_color = [1, 0, 0, 1] - var base_hsv = Color.rgb2hsv(base_color) - var base_value_gamma = 1.6 - var base_sat_gamma = 0.8 - var triangle_size = 35 //this is the inner triangle radius - var outer_ring_size = 200 - var inner_ring_size = 140 + var data = ColorPickerData.new() //setup root var panel = UIWindow.create(ui) - Control.set_size(panel, 350, 400) + Control.set_size(panel, 350, 500) Control.set_id(panel, "panel.%(ID.unique())") //Control.set_state_data(panel, base_color) //turns out UIWindows use their own state, whoops @@ -27,20 +130,190 @@ class ColorPicker{ Control.set_margin(color_view, 0, 40, 0, 0) Control.child_add(panel, color_view) Control.set_id(panel, "color_view.%(ID.unique())") - Control.set_state_data(color_view, base_color) //stores state "golbally" for multiple elements in the colorpicker to access + Control.set_state_data(color_view, data) var hsv_view = Control.create(ui) Control.set_behave(hsv_view, UIBehave.fill) Control.set_margin(hsv_view, 0, 0, 0, 0) + Control.set_contain(hsv_view, UIContain.column | UIContain.start) Control.child_add(color_view, hsv_view) Control.set_id(panel, "hsv_view.%(ID.unique())") + var wheel = create_hsv_wheel(ui, color_view) + Control.set_behave(wheel, UIBehave.left | UIBehave.top) + Control.set_margin(wheel, 8, 8, 0, 0) + Control.child_add(hsv_view, wheel) + + var component_choice = Control.create(ui) + Control.set_contain(component_choice, UIContain.row | UIContain.start) + Control.set_behave(component_choice, UIBehave.hfill | UIBehave.left | UIBehave.top) + Control.set_margin(component_choice, 8, 8, 0, 0) + Control.set_size(component_choice, 0, 32) + Control.child_add(hsv_view, component_choice) + + var rgba_button = UIButton.create(ui) + Control.set_behave(rgba_button, UIBehave.left) + UIButton.set_text(rgba_button, "rgb") + Control.child_add(component_choice, rgba_button) + + var hsva_button = UIButton.create(ui) + Control.set_behave(hsva_button, UIBehave.left) + UIButton.set_text(hsva_button, "hsv") + Control.child_add(component_choice, hsva_button) + + var rgba_components = rgba_values(ui, color_view) + Control.child_add(hsv_view, rgba_components) + + var hsva_components = hsva_values(ui, color_view) + Control.child_add(hsv_view, hsva_components) + + Control.set_events(rgba_button) {|event| + if(event.type == UIEvent.press){ + Control.set_visible(rgba_components, true) + Control.set_visible(hsva_components, false) + } + } + + Control.set_events(hsva_button) {|event| + if(event.type == UIEvent.press){ + Control.set_visible(rgba_components, false) + Control.set_visible(hsva_components, true) + } + } + + //todo: next steps: hex input(s?) + + return panel + } + + static rgba_values(ui: UI, color_view: Control) { + var rgba_components = Control.create(ui) + Control.set_contain(rgba_components, UIContain.column | UIContain.hfit | UIContain.vfit) + Control.set_behave(rgba_components, UIBehave.hfill) + Control.set_margin(rgba_components, 0, 8, 0, 0) + + var red = color_component(ui, "R", 0, color_view, "rgb") + Control.child_add(rgba_components, red) + var green = color_component(ui, "G", 1, color_view, "rgb") + Control.child_add(rgba_components, green) + var blue = color_component(ui, "B", 2, color_view, "rgb") + Control.child_add(rgba_components, blue) + var alpha = color_component(ui, "A", 3, color_view, "rgb") + Control.child_add(rgba_components, alpha) + + return rgba_components + } + + static hsva_values(ui: UI, color_view: Control) { + var rgba_components = Control.create(ui) + Control.set_contain(rgba_components, UIContain.column | UIContain.hfit | UIContain.vfit) + Control.set_behave(rgba_components, UIBehave.hfill) + Control.set_margin(rgba_components, 0, 8, 0, 0) + + var red = color_component(ui, "H", 0, color_view, "hsv") + Control.child_add(rgba_components, red) + var green = color_component(ui, "S", 1, color_view, "hsv") + Control.child_add(rgba_components, green) + var blue = color_component(ui, "V", 2, color_view, "hsv") + Control.child_add(rgba_components, blue) + var alpha = color_component(ui, "A", 3, color_view, "hsv") + Control.child_add(rgba_components, alpha) + + return rgba_components + } + + static color_component (ui: Entity, name: String, index: Num, color_view: Control, space: String){ + var base = Control.create(ui) + Control.set_size(base, 0, 32) + Control.set_behave(base, UIBehave.top | UIBehave.left | UILayoutBehave.hfill) + Control.set_contain(base, UIContain.row) + Control.set_margin(base, 0, 0, 0, 0) + Control.set_id(base, "component.%(name).%(ID.unique())") + + var label = UILabel.create(ui) + UILabel.set_text(label, name) + UILabel.set_text_size(label, 20) + Control.set_size(label, 20, 32) + Control.set_behave(label, UIBehave.left | UIBehave.vfill) + Control.set_margin(label, 8, 0, 8, 0) + Control.child_add(base, label) + + var number = UINumber.create(ui) + UINumber.set_value(number, 0) + UINumber.set_validation(number) {|input| + return Math.fixed(input) + } + Control.set_behave(number, UIBehave.top | UIBehave.left) + Control.set_margin(number, 0, 0, 0, 0) + Control.set_size(number, 72, 32) + Control.child_add(base, number) + + var slider = UISlider.create(ui) + UISlider.set_min(slider, 0) + UISlider.set_max(slider, 1) + UISlider.set_value(slider, 0) + Control.set_behave(slider, UILayoutBehave.top | UILayoutBehave.bottom | UILayoutBehave.hfill) + Control.set_margin(slider, 8, 8, 8, 8) + Control.child_add(base, slider) + + Control.set_events(number) { |event: UIEvent| + if(event.type == UIEvent.change){ + if(approx(UISlider.get_value(slider), event.change, 0.001)) return + System.print(" Base event (slider)") + UI.events_emit(base, UIEvent.change, event.change) + } + } + + Control.set_events(slider) { |event: UIEvent| + if(event.type == UIEvent.change){ + if(approx(UINumber.get_value(number), event.change, 0.001)) return + System.print(" Base event (number)") + UI.events_emit(base, UIEvent.change, event.change) + } + } + + Control.set_events(color_view){|event| + if(event.type == UIEvent.change){ + //System.print(event.change) + if(space == "rgb"){ + UISlider.set_value(slider, Math.fixed(event.change.get_rgba_component(index))) + UINumber.set_value(number, Math.fixed(event.change.get_rgba_component(index))) + } else if(space == "hsv"){ + UISlider.set_value(slider, Math.fixed(event.change.get_hsva_component(index))) + UINumber.set_value(number, Math.fixed(event.change.get_hsva_component(index))) + } + } + } + + Control.set_events(base) {|event| + if(event.type == UIEvent.change){ + var color = Control.get_state_data(color_view) + if(space == "rgb"){ + if(approx(event.change, color.get_rgba_component(index))) return + color.set_rgba_component(index, event.change) + } else if(space == "hsv"){ + if(approx(event.change, color.get_hsva_component(index))) return + color.set_hsva_component(index, event.change) + } + Control.set_state_data(color_view, color) + System.print("event from color component (%(name))") + UI.events_emit(color_view, UIEvent.change, color) + } + } + + return base + } + + static create_hsv_wheel(ui: Entity, data_root: Control) : Control { + var data: ColorPickerData = Control.get_state_data(data_root) + var base_value_gamma = 1.6 + var base_sat_gamma = 0.8 + var color_wheel = Control.create(ui) - Control.set_size(color_wheel, outer_ring_size, outer_ring_size) - Control.child_add(hsv_view, color_wheel) + Control.set_size(color_wheel, data.outer_ring_size, data.outer_ring_size) + //Control.child_add(parent, color_wheel) //the wheel has a bunch of state to have stuff be more solid (for example not reset hue when going to [0,0,0,_]) and interaction state - Control.set_state_data(color_wheel, {"ring":null, "triangle":null, - "hue": base_hsv.x, "value": base_hsv.z, "saturation": base_hsv.y}) + Control.set_state_data(color_wheel, {"ring":null, "triangle":null}) Control.set_process(color_wheel){|control, state, event, x,y,w,h| //this might not be nessecary anymore? if(event.control != control) return @@ -55,18 +328,17 @@ class ColorPicker{ var hue = angle / Num.tau //change relevant values - var color = Control.get_state_data(color_view) - var hsv = [hue, state["saturation"], state["value"], color.a] - state["hue"] = hue - color = Color.hsv2rgb(hsv) - Control.set_state_data(control, state) - Control.set_state_data(color_view, color) - UI.events_emit(control, UIEvent.change, state) - UI.events_emit(color_view, UIEvent.change, color) + var picker_state: ColorPickerData = Control.get_state_data(data_root) + var hsv = [hue, picker_state.s, picker_state.v, picker_state.a] + picker_state.set_hsva(hsv) + System.print("Event from hue ring") + UI.events_emit(data_root, UIEvent.change, picker_state) } else if(state["triangle"] == "captured"){ //if we're editing the triangle (saturation & value) + var picker_state: ColorPickerData = Control.get_state_data(data_root) var diff = [event.x - center.x, event.y - center.y] //position rel to center - Math.rotate(diff, 0, 0, state["hue"] * -360) //follow triangle rotation + Math.rotate(diff, 0, 0, picker_state.h * -360) //follow triangle rotation //constrain to triangle + var triangle_size = picker_state.triangle_size constrain_to_triangle(diff, triangle_size) //get saturation based on distance to 2 edges @@ -83,26 +355,20 @@ class ColorPicker{ value = value.clamp(0, 1) //calculate and apply relevant values - var hue = state["hue"] - var color = Control.get_state_data(color_view) - var hsv = [hue, saturation, value, color.a] - hsv[0] = hue - state["value"] = value - state["saturation"] = saturation - color = Color.hsv2rgb(hsv) - Control.set_state_data(control, state) - Control.set_state_data(color_view, color) - UI.events_emit(control, UIEvent.change, state) - UI.events_emit(color_view, UIEvent.change, color) + var hsv = [picker_state.h, saturation, value, picker_state.a] + picker_state.set_hsva(hsv) + System.print("event from saturation/value triangle") + UI.events_emit(data_root, UIEvent.change, picker_state) } else { //if we're not editing anything, lets check what we're hovering over! + var picker_state: ColorPickerData = Control.get_state_data(data_root) var distance = Math.dist2D(center, event) //first reset hover state (can only be hover bc we checked for captured) state["ring"] = null state["triangle"] = null //then find actual state - if(distance > inner_ring_size/2 && distance < outer_ring_size/2){ + if(distance > picker_state.inner_ring_size/2 && distance < picker_state.outer_ring_size/2){ state["ring"] = "hover" - } else if(distance < inner_ring_size/2) { //todo: triangle not round + } else if(distance < picker_state.inner_ring_size/2) { //todo: triangle not round state["triangle"] = "hover" } } @@ -114,6 +380,7 @@ class ColorPicker{ if(hover_ring) state["ring"] = "captured" if(hover_tri) state["triangle"] = "captured" } else if(event.type == UIEvent.release && event.button == 1) { //if we release a click, let go + var picker_state: ColorPickerData = Control.get_state_data(data_root) x = Control.get_pos_x_abs(control) y = Control.get_pos_y_abs(control) var center = [x + w/2, y + h/2] @@ -123,60 +390,41 @@ class ColorPicker{ state["ring"] = null state["triangle"] = null //then find actual state - if(distance > inner_ring_size/2 && distance < outer_ring_size/2){ + if(distance > picker_state.inner_ring_size/2 && distance < picker_state.outer_ring_size/2){ state["ring"] = "hover" - } else if(distance < inner_ring_size/2) { //todo: triangle not round + } else if(distance < picker_state.inner_ring_size/2) { //todo: triangle not round state["triangle"] = "hover" } UI.uncapture(control) } } Control.set_allow_input(color_wheel, true) - //update values when some other UI that doesnt care about HSV changes the color - Control.set_events(color_view) {|event| - if(event.type == UIEvent.change){ - //construct wheel color - var wheel_data = Control.get_state_data(color_wheel) - var wheel_color = Color.hsv2rgb([wheel_data["hue"], wheel_data["saturation"], wheel_data["value"], 1]) - //ignore if its the same (we likely triggered this ourselves) (though this still fails often when 2 multiple land in the event queue) - if(approx_rgb(wheel_color, event.change, 0.001)) return - //update the wheel hsv if different - var hsv = Color.rgb2hsv(event.change) - //only change hue if saturation is nonzero and theres relevant change (saturation check is bc in rgb greyscale has no hue) - if(hsv.y > 0.001 && !approx(wheel_data["hue"], hsv.x, 0.001)) wheel_data["hue"] = hsv.x - //only change if theres relevant change - if(!approx(wheel_data["saturation"], hsv.y, 0.001)) wheel_data["saturation"] = hsv.y - if(!approx(wheel_data["value"], hsv.z, 0.001)) wheel_data["value"] = hsv.z - //also trigger event in self to update stuff like image rotation - UI.events_emit(color_wheel, UIEvent.change, wheel_data) - } - } //ring visuals, interresting stuff happens in shader var color_ring = UIImage.create(ui) - Control.set_size(color_ring, outer_ring_size, outer_ring_size) + Control.set_size(color_ring, data.outer_ring_size, data.outer_ring_size) Control.set_contain(color_ring, UIContain.justify) var color_ring_mat = Material.create("materials/color_ring") Material.set_input(color_ring_mat, "wheel.outer_distance", 0.5) - Material.set_input(color_ring_mat, "wheel.inner_distance", 0.5 * inner_ring_size / outer_ring_size) + Material.set_input(color_ring_mat, "wheel.inner_distance", 0.5 * data.inner_ring_size / data.outer_ring_size) UIImage.set_material(color_ring, color_ring_mat) Control.child_add(color_wheel, color_ring) //triangle visual, interresting stuff happens in shader var color_triangle = UIImage.create(ui) Control.set_margin(color_triangle, 30, 30, 0, 0) - Control.set_size(color_triangle, triangle_size * 4, triangle_size*4) + Control.set_size(color_triangle, data.triangle_size * 4, data.triangle_size*4) Control.set_contain(color_triangle, UIContain.justify) var color_tri_mat = Material.create("materials/color_triangle") Material.set_input(color_tri_mat, "triangle.value_gamma", base_value_gamma) Material.set_input(color_tri_mat, "triangle.saturation_gamma", base_sat_gamma) - Material.set_input(color_tri_mat, "triangle.hue", base_hsv.x) + Material.set_input(color_tri_mat, "triangle.hue", data.h) UIImage.set_material(color_triangle, color_tri_mat) Control.child_add(color_wheel, color_triangle) //rotates image when color wheel hue updates - Control.set_events(color_wheel) {|event| + Control.set_events(data_root) {|event| if(event.type == UIEvent.change){ - var hue = event.change["hue"] + var hue = event.change.h UIImage.set_angle(color_triangle, hue * -360) Material.set_input(color_tri_mat, "triangle.hue", hue) } @@ -192,20 +440,22 @@ class ColorPicker{ var center = [x + w/2, y + h/2] var style: PathStyle = PathStyle.new() - var color = Control.get_state_data(color_view) - var wheel_data = Control.get_state_data(color_wheel) - var hue = wheel_data["hue"] - var value = wheel_data["value"].pow(1/base_value_gamma) - var saturation = wheel_data["saturation"].pow(1/base_sat_gamma) + var picker_state: ColorPickerData = Control.get_state_data(data_root) + var color = picker_state.rgba + color.a = 1 + var hue = picker_state.h + var saturation = picker_state.s.pow(1/base_sat_gamma) + var value = picker_state.v.pow(1/base_value_gamma) //draw hue ring borders style.color = [0.22, 0.22, 0.22, 1] - style.thickness = 2 - UI.draw_ring(control, center.x, center.y, depth, outer_ring_size/2, outer_ring_size/2, 0, 360, 8, style) - UI.draw_ring(control, center.x, center.y, depth, inner_ring_size/2, inner_ring_size/2, 0, 360, 8, style) + style.thickness = 3 + UI.draw_ring(control, center.x, center.y, depth, picker_state.outer_ring_size/2, picker_state.outer_ring_size/2, 0, 360, 8, style) + UI.draw_ring(control, center.x, center.y, depth, picker_state.inner_ring_size/2, picker_state.inner_ring_size/2, 0, 360, 8, style) //draw hue ring color rect - style.thickness = 3 + var wheel_data = Control.get_state_data(color_wheel) + style.thickness = 2 if(wheel_data["ring"] != null) { style.color = [0.5, 0.5, 0.5, 1] } else { @@ -214,18 +464,19 @@ class ColorPicker{ var size = [20, 30] var angle_degree = hue * -360 var angle_radian = hue * -Num.tau - var avg_radius = (inner_ring_size + outer_ring_size) / 2 / 2 + var avg_radius = (picker_state.inner_ring_size + picker_state.outer_ring_size) / 2 / 2 var offset = [-angle_radian.sin * avg_radius - size.x/2, -angle_radian.cos * avg_radius - size.y/2] UI.draw_quad(control, center.x + offset.x, center.y + offset.y, depth, size.x, size.y, angle_degree, color) UI.draw_rect(control, center.x + offset.x, center.y + offset.y, depth, size.x, size.y, angle_degree, style) //draw triangle color dot - style.thickness = 3 + style.thickness = 2 if(wheel_data["triangle"] != null) { style.color = [0.5, 0.5, 0.5, 1] } else { style.color = [0.22, 0.22, 0.22, 1] } + var triangle_size = picker_state.triangle_size var v_dist = (1 - value) * (triangle_size * 3) - triangle_size //distance in "value direction" in triangle var v_dir = dir_vec(Math.radians(150), 1) //"value direction" var v_norm = [-v_dir.y, v_dir.x] //direction orthogonal to "value direction" @@ -239,88 +490,20 @@ class ColorPicker{ } Control.child_add(color_wheel, color_wheel_overlay) - return panel - - /* - - //setup colorspace buttons - //todo: this should be a dropdown??? - var colorspace_list = UIList.create(ui) //could use naked control but logically its a list? - Control.child_add(root, colorspace_list) - //base button size is 92x32 btw - var space_button_height = 30 - var space_button_width = 50 - for(i in 0...Color_spaces.count){ - var space = Color_spaces[i] - var colorspace_button = UIButton.create(ui) - Control.set_size(colorspace_button, space_button_width, space_button_height) - UIList.add(colorspace_list, colorspace_button) - UIButton.set_text(colorspace_button, space["name"]) - Control.set_pos(colorspace_button, i * space_button_width, 0) //manually set position - //todo: add event to actually do stuff here - } - Control.set_size(colorspace_list, Control.get_width(root).min(space_button_width * Color_spaces.count), space_button_height) - //UIList.refresh(colorspace_list) //uilist can only do vertical ordering so far :( - - //setup uimode buttons - //todo: this should be a dropdown??? - var mode_list = UIList.create(ui) - Control.child_add(root, mode_list) - Control.set_pos(mode_list, 0, space_button_height) - //base button size is 92x32 btw - var mode_button_height = 30 - var mode_button_width = 50 - for(i in 0...Modes.count){ - var mode = Modes[i] - var mode_button = UIButton.create(ui) - Control.set_size(mode_button, mode_button_width, mode_button_height) - UIList.add(mode_list, mode_button) - UIButton.set_text(mode_button, mode["icon"]) - Control.set_pos(mode_button, i * mode_button_width, 0) //manually set position - //todo: add event to actually do stuff here - } - Control.set_size(mode_list, Control.get_width(root).min(mode_button_width * Modes.count), mode_button_height) - - //setup main editor - var main_editor_root = Control.create(ui) - Control.child_add(root, main_editor_root) - Control.set_size(main_editor_root, 200, 200) - Control.set_pos(main_editor_root, 0, mode_button_height + space_button_height) - //todo: configurable default editor - build_triangle_editor(ui, main_editor_root) - - //setup sliders - var sliders_root = Control.create(ui) - Control.child_add(root, sliders_root) - Control.set_size(sliders_root, 300, 140) - Control.set_pos(sliders_root, 0, mode_button_height + space_button_height + 200) - - //setup text input/output - var text_io_root = Control.create(ui) - Control.child_add(root, text_io_root) - Control.set_size(text_io_root, 150, 100) - Control.set_pos(text_io_root, 200, 160) - - //setup preview field (old => new) - var preview_root = Control.create(ui) - Control.child_add(root, preview_root) - Control.set_size(preview_root, 150, 100) - Control.set_pos(preview_root, 200, 60) - - - //other stuff? gamma/linear? scene color pick? range toggle (0-1/0-255)? HDR?? - //todo - - return root - - */ + return color_wheel } static approx_rgb(one: Color, other: Color, epsilson: Num) : Bool{ return approx(one.x, other.x, epsilson) && approx(one.y, other.y, epsilson) && approx(one.z, other.z, epsilson) } + + static approx(one: Num, other: Num) : Bool{ + return approx(one, other, 0.001) + } + static approx(one: Num, other: Num, epsilon: Num) : Bool{ + if(!(one is Num && other is Num)) return false return (one - other).abs < epsilon }