Created
June 20, 2020 21:01
-
-
Save Skaruts/887629881d4667a5ec71ce03efb26630 to your computer and use it in GitHub Desktop.
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
# type | |
# UI = ref object | |
include ../prelude | |
import ../terminal | |
import ../lib/rexnim | |
import theme | |
import tables, sequtils | |
import strutils | |
include ui_control | |
include ui_base_button | |
include ui_label | |
proc label*(x, y:int, text:string = "", style = def_theme.label):Label | |
include ui_icon | |
proc icon*(x, y:int, icon:int, style = def_theme.icon):Icon | |
include ui_button | |
proc button*(x, y:int, text:string, style = def_theme.button):Button | |
proc button*(x, y:int, icon:int, style = def_theme.button):Button | |
proc button*(x, y:int, text:string, icon:int, style = def_theme.button):Button | |
# include ui_icon_button | |
# proc icon_button*(x, y:int, icon:int, style = def_theme.button):IconButton | |
include ui_toggle_button | |
proc toggle_button*(x, y:int, style = def_theme.toggle):ToggleButton | |
include ui_frame | |
proc frame*(x, y, w, h:int):Frame | |
include ui_panel | |
proc panel*(x, y, w, h:int, style = def_theme.panel):Panel | |
include ui_container | |
proc container*(x, y, w, h:int):Container | |
include ui_button_group | |
proc button_group*(x, y, w, h:int):ButtonGroup | |
include ui_slider | |
proc vslider*(x, y, length:int, val:var int, minv, maxv:int, style = def_theme.slider):VSlider | |
include ui_color_bar | |
proc color_bar*(x, y, length:int, val:int, color_channel = ColorChannel.Red):ColorBar | |
proc label*(x, y:int, text:string = "", style = def_theme.label):Label = | |
new result | |
result.super(x, y, text, style) | |
proc icon*(x, y:int, icon:int, style = def_theme.icon):Icon = | |
new result | |
result.super(x, y, icon, style) | |
proc button*(x, y:int, text:string, style = def_theme.button):Button = | |
new result | |
result.super(x, y, text, style) | |
proc button*(x, y:int, icon:int, style = def_theme.button):Button = | |
new result | |
result.super(x, y, icon, style) | |
proc button*(x, y:int, text:string, icon:int, style = def_theme.button):Button = | |
new result | |
result.super(x, y, text, icon, style) | |
# proc icon_button*(x, y:int, icon:int, style = def_theme.button):IconButton = | |
# new result | |
# result.super(x, y, icon, style) | |
proc toggle_button*(x, y:int, style = def_theme.toggle):ToggleButton = | |
new result | |
result.super(x, y, style) | |
proc panel*(x, y, w, h:int, style = def_theme.panel):Panel = | |
new result | |
result.super(x, y, w, h, style) | |
proc frame*(x, y, w, h:int):Frame = | |
new result | |
result.super(x, y, w, h) | |
proc container*(x, y, w, h:int):Container = | |
new result | |
result.super(x, y, w, h) | |
proc button_group*(x, y, w, h:int):ButtonGroup = | |
new result | |
result.super(x, y, w, h) | |
proc vslider*(x, y, length:int, val:var int, minv, maxv:int, style = def_theme.slider):VSlider = | |
new result | |
result.super(x, y, length, val, minv, maxv, style) | |
proc color_bar*(x, y, length:int, val:int, color_channel = ColorChannel.Red):ColorBar = | |
new result | |
result.super(x, y, length, val, color_channel) |
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
type | |
# Callback = proc() | |
# Signal = ref object | |
# target:string | |
# # name:string | |
# fn:Callback | |
MouseMode* = enum | |
Stop, Pass, Ignore | |
Control* = ref object of RootObj | |
name*:string | |
pos_x, pos_y, width, height:int | |
fg*, bg*:Color | |
mouse_mode*:MouseMode | |
parent*:Control | |
# signals:seq[string] | |
mouse_over:bool | |
children:seq[Control] | |
expand_h*, expand_v*:bool | |
min_w*, min_h*:int | |
# theme:Theme | |
# use_parent_theme:bool | |
colors*:Style | |
is_active*, is_visible*:bool | |
is_hovered:bool | |
was_hovered_last_frame:bool | |
signals:seq[string] | |
callbacks:Table[string, proc()] | |
# converter to_proc*(poo:void):proc = | |
# return proc() = poo | |
# TODO MAYBE: | |
# flags (CALL_ONCE, etc) | |
# call deferred (might need a signal manager for this?) | |
# allow differently typed parameters (https://forum.nim-lang.org/t/1440 | https://stackoverflow.com/questions/48418386/tuple-to-function-arguments-in-nim) | |
template hash_signal[T](self:Control, signal:string, target_id:T, fn_name:string):string = | |
signal .. target_id .. fn_name | |
proc connect*[T](self:Control, signal:string, target_id:T, fn_name:string, fn:proc()) = | |
if self.signals.find(signal) == -1: | |
assert( false, fmt "\n\nERROR: can't connect signal: '{signal}' does not exist in class {self.name}\n\n" ) | |
else: | |
self.callbacks[self.hash_signal(signal, target_id, fn_name)] = fn | |
proc disconnect*[T](self:Control, signal:string, target_id:T, fn_name:string, fn:proc()) = | |
let name = self.hash_signal(signal, target_id, fn_name) | |
if self.signals.find(signal) == -1: assert( false, fmt "\n\nERROR: can't connect signal: '{signal}' does not exist in class {self.name}\n\n" ) | |
elif self.callbacks.has_key(name): | |
self.callbacks.del(name) | |
proc emit_signal*(self:Control, signal:string) = | |
if self.signals.find(signal) == -1: assert( false, fmt "\n\nERROR: can't emit signal: '{signal}' does not exist in class {self.name}\n\n" ) | |
else: | |
var sigs = self.callbacks # prevent user altering the table (by disconnecting signal in the callback) while looping over it | |
for k, v in sigs: | |
if k.startsWith(signal): | |
v() | |
template `size`*(self:Control):Vector2i = | |
sf_Vector2(self.w, self.h) | |
template `size=`*(self:Control, size:Vector2i) = | |
self.width = max(size.x, self.min_w) | |
self.height = max(size.y, self.min_h) | |
self.emit_signal("resized") | |
template `w`*(self:Control):int = self.width | |
template `w=`*(self:Control, new_w:int) = | |
self.width = max(new_w, self.min_w) | |
self.emit_signal("resized") | |
template `h`*(self:Control):int = self.height | |
template `h=`*(self:Control, new_h:int) = | |
self.height = max(new_h, self.min_h) | |
self.emit_signal("resized") | |
template `x`*(self:Control):int = self.pos_x | |
template `x=`*(self:Control, new_x:int) = | |
self.pos_x = new_x | |
self.emit_signal("repositioned") | |
template `y`*(self:Control):int = self.pos_y | |
template `y=`*(self:Control, new_y:int) = | |
self.pos_y = new_y | |
self.emit_signal("repositioned") | |
template `pos`*(self:Control):Vector2i = | |
sf_Vector2(self.pos_x, self.pos_y) | |
template `pos=`*(self:Control, pos:Vector2i) = | |
self.pos_x = pos.x | |
self.pos_y = pos.y | |
self.emit_signal("repositioned") | |
proc global_pos(self:Control):Vector2i = | |
if not is_nil(self.parent): | |
self.pos + self.parent.global_pos | |
else: | |
self.pos | |
proc `$`*(self:Control):string | |
proc super(self:Control, x, y:int) | |
method update*(self:Control, dt:float) {.base.} | |
method draw*(self:Control, terminal:Terminal) {.base.} | |
# proc get_global_pos*(self:Control):Vector2i | |
proc propagate_input(self:Control, event:Event) | |
method input*(self:Control, event:Event) {.base.} | |
proc on_mouse_entered(self:Control, emit = true) | |
proc on_mouse_exited(self:Control, emit = true) | |
proc `visible`*(self:Control):bool | |
proc `visible=`*(self:Control, enable:bool) | |
proc `active`*(self:Control):bool | |
proc `active=`*(self:Control, enable:bool) | |
proc `$`*(self:Control):string = | |
fmt "Control({self.pos_x}, {self.pos_y} | {self.width}, {self.height})" | |
proc super(self:Control, x, y:int) = | |
# self.signal_fns = initTable[string, Callback]() | |
self.signals = @[ | |
"resized", | |
"repositioned", | |
"visibility_changed", | |
"activated", | |
"deactivated", | |
"mouse_entered", | |
"mouse_exited" | |
] | |
self.name = "Control" | |
self.pos_x = x | |
self.pos_y = y | |
self.width = 0 | |
self.height = 0 | |
# self.style = style | |
self.is_active = true | |
self.is_visible = true | |
self.mouse_mode = Pass | |
self.mouse_over = false | |
self.is_hovered = false | |
self.was_hovered_last_frame = false | |
# self.theme = theme | |
# self.use_parent_theme = true | |
self.parent = nil | |
self.children = newSeq[Control]() | |
self.expand_h = false | |
self.expand_v = false | |
self.min_w = 0 | |
self.min_h = 0 | |
# proc get_theme(self:Control):Theme = | |
# if self.parent: return self.parent.get_theme() | |
# else: return self.theme | |
proc sort_colors(self:Control) = | |
if self.is_active: | |
self.fg = LabelStyle(self.colors).fg | |
self.bg = LabelStyle(self.colors).bg | |
else: | |
self.fg = def_theme.fg_disabled | |
self.bg = def_theme.bg_disabled | |
template `style`*(self:Control):LabelStyle = LabelStyle(self.colors) | |
template `style=`*(self:Control, st:Style) = | |
self.colors = st | |
self.sort_colors() | |
# TODO: controls could draw themselves into a buffer in the UI object, which would then in turn batch to the terminal | |
method update*(self:Control, dt:float) {.base.} = | |
if self.is_visible and self.is_active: | |
for c in self.children: | |
c.update(dt) | |
method draw*(self:Control, terminal:Terminal) {.base.} = | |
# echo "drawing control - ", self.name | |
if self.is_visible: | |
for c in self.children: | |
# if self.name == "ItemList": | |
# echo "this is item list, ", len(self.children), ", ", self | |
# echo c, "|", c.name, ", ", c.global_pos | |
c.draw(terminal) | |
# const KEY_EVENTS = [EventType.KeyPressed, EventType.KeyReleased] | |
# const MOUSE_EVENTS = [EventType.MouseMoved, EventType.MouseWheelMoved, EventType.MouseButtonPressed, EventType.MouseButtonReleased] | |
# proc get_global_pos*(self:Control):Vector2i = | |
# return sf_Vector2(self.x, self.y) | |
template check_mouse(self:Control):bool = | |
let mpos = settings.mouse_pos | |
let gpos = self.global_pos | |
mpos.x >= gpos.x and mpos.x < gpos.x+self.w and | |
mpos.y >= gpos.y and mpos.y < gpos.y+self.h | |
proc on_mouse_entered(self:Control, emit = true) = | |
if emit: self.emit_signal("mouse_entered") | |
# echo "Control.on_mouse_entered" | |
# discard | |
proc on_mouse_exited(self:Control, emit = true) = | |
if emit: self.emit_signal("mouse_exited") | |
# echo "Control.on_mouse_exited" | |
# discard | |
# proc input*(self:Control, event:Event) = | |
method input*(self:Control, event:Event) {.base.} = | |
# echo "Control.input" | |
if self.active and self.is_visible: | |
if event.kind in [MouseMoved, MouseButtonPressed, MouseButtonReleased]: | |
self.is_hovered = self.check_mouse() | |
if self.is_hovered: | |
if not self.was_hovered_last_frame: | |
self.on_mouse_entered() | |
self.propagate_input(event) | |
elif self.was_hovered_last_frame: | |
self.on_mouse_exited() | |
self.propagate_input(event) | |
self.was_hovered_last_frame = self.is_hovered | |
else: | |
self.was_hovered_last_frame = false | |
proc propagate_input(self:Control, event:Event) = | |
if self.mouse_mode != Stop: | |
# var input_candidates = newSeq[Control]() | |
# for c in self.children: | |
# if c.was_hovered_last_frame or c.check_mouse(): # TODO: 'c.is_hovered' instead? | |
# input_candidates.add(c) | |
# if len(input_candidates) > 0: | |
# for c in input_candidates: | |
# if c.mouse_mode != Ignore: | |
# c.input(event) | |
# if c.mouse_mode == Stop: | |
# break | |
for c in self.children: | |
if c.mouse_mode != Ignore: | |
c.input(event) | |
if c.mouse_mode == Stop: | |
break | |
proc add_child(self, c:Control):Control {.discardable.} = | |
if not isNil(c.parent): echo fmt "WARNING: Control.add_child: {c.name} already has a parent." | |
elif c in self.children: echo fmt "WARNING: Control.add_child: {c.name} is already a child of {self.name}." | |
else: | |
c.parent = self | |
self.children.add(c) | |
return c | |
proc remove_child(self, c:Control):Control {.discardable.} = | |
assert(false, "Control.remove_child: not implemented yet") | |
return c | |
proc `visible`*(self:Control):bool = self.is_visible | |
proc `visible=`*(self:Control, enable:bool) = | |
if self.is_visible != enable: | |
self.is_visible = enable | |
self.emit_signal("visibility_changed") | |
if self.is_hovered: | |
if self.is_visible: self.emit_signal("mouse_entered") | |
else: self.emit_signal("mouse_exited") | |
proc `active`*(self:Control):bool = | |
if not self.is_active: return false | |
if not is_nil(self.parent): return self.parent.active | |
return true | |
proc `active=`*(self:Control, enable:bool) = | |
if self.is_active != enable: | |
self.is_active = enable | |
if self.is_active: self.emit_signal("activated") | |
else: self.emit_signal("deactivated") | |
if self.is_hovered: | |
if self.is_active: self.emit_signal("mouse_entered") | |
else: self.emit_signal("mouse_exited") | |
template show*(self:Control) = self.set_visible(true) | |
template hide*(self:Control) = self.set_visible(false) | |
template activate*(self:Control) = self.set_active(true) | |
template deactivate*(self:Control) = self.set_active(false) | |
# template `rect`(self:Control, r:Recti) = | |
# self.pos = |
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
type | |
Label* = ref object of Control | |
str:string | |
proc `$`*(self:Label):string = | |
fmt "Label({self.x}, {self.y} | {self.w}, {self.h})" | |
template `text`*(self:Label):string = self.str | |
template `text=`*(self:Label, text:string) = | |
self.str = text | |
self.w = len(text) | |
# template style*(self:Label):LabelStyle = LabelStyle(self.colors) | |
# template `style=`*(self:Label, st:Style) = self.colors = st | |
proc super(self:Label, x, y:int, text:string, style = def_theme.label) = | |
self.Control.super(x, y) | |
self.name = "Label" | |
self.style = style | |
self.h = 1 | |
self.text = text | |
method draw*(self:Label, terminal:Terminal) = | |
if self.visible: | |
let gpos = self.global_pos | |
terminal.print(gpos.x, gpos.y, self.text, self.fg, self.bg) | |
# else: terminal.print(gpos.x, gpos.y, self.text, self.style.fg_disabled, self.style.bg_disabled) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment