Skip to content

Instantly share code, notes, and snippets.

@Skaruts
Created June 20, 2020 21:01
Show Gist options
  • Save Skaruts/887629881d4667a5ec71ce03efb26630 to your computer and use it in GitHub Desktop.
Save Skaruts/887629881d4667a5ec71ce03efb26630 to your computer and use it in GitHub Desktop.
# 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)
type
# Callback = proc()
BaseButton* = ref object of Control
is_pressed:bool
is_toggle*:bool
# fg:Color
# bg:Color
proc `$`*(self:BaseButton):string
proc super(self:BaseButton, x, y:int, style = def_theme.button)
proc set_toggle*(self:BaseButton, emit=false)
proc on_mouse_entered(self:BaseButton, emit = true)
proc on_mouse_exited(self:BaseButton, emit = true)
proc on_pressed(self:BaseButton, emit = true)
proc on_released(self:BaseButton, emit = true)
# proc set_active*(self:BaseButton, enable:bool)
proc on_input(self:BaseButton, event:Event)
method input*(self:BaseButton, event:Event)
# proc set_action*(b:BaseButton, on_event:string, cbk:Callback)
proc `$`*(self:BaseButton):string =
fmt "BaseButton({self.x}, {self.y} | {self.w}, {self.h})"
proc sort_colors(self:BaseButton) =
if likely(self.is_active):
if unlikely(self.is_pressed):
if unlikely(self.is_hovered):
self.fg = ButtonStyle(self.colors).fg_pressed_h
self.bg = ButtonStyle(self.colors).bg_pressed_h
else:
self.fg = ButtonStyle(self.colors).fg_pressed
self.bg = ButtonStyle(self.colors).bg_pressed
else:
if unlikely(self.is_hovered):
self.fg = ButtonStyle(self.colors).fg_normal_h
self.bg = ButtonStyle(self.colors).bg_normal_h
else:
self.fg = ButtonStyle(self.colors).fg_normal
self.bg = ButtonStyle(self.colors).bg_normal
else:
self.fg = def_theme.fg_disabled
self.bg = def_theme.bg_disabled
template style*(self:BaseButton):ButtonStyle =
ButtonStyle(self.colors)
# proc `style=`*(self:Label, st:Style) = self.colors = st
template `style=`*(self:BaseButton, st:Style) =
self.colors = st
self.sort_colors()
proc super(self:BaseButton, x, y:int, style = def_theme.button) =
self.Control.super(x, y)
self.h = 1
self.signals = self.signals.concat(@[
"toggled",
"pressed",
"released",
])
self.name = "BaseButton"
self.is_pressed = false
self.is_toggle = false
self.style = style
proc set_toggle*(self:BaseButton, emit=false) =
if self.is_toggle:
if self.is_pressed: self.on_released(emit)
else: self.on_pressed(emit)
method input*(self:BaseButton, event:Event) =
# echo "BaseButton.input"
if event.kind notin [KeyPressed, KeyReleased] and self.active and self.visible:
# self.debug()
# if event.kind notin [KeyPressed, KeyReleased]:
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)
if event.kind in [MouseButtonPressed, MouseButtonReleased] and
event.mouseButton.button == MouseButton.Left:
self.on_input(event)
self.was_hovered_last_frame = self.is_hovered == true
else:
self.was_hovered_last_frame = false
proc on_input(self:BaseButton, event:Event) =
# echo "BaseButton.on_input"
if self.mouse_mode != Ignore:
if self.is_hovered:
if not self.is_toggle:
# echo event.kind, " ", self.is_pressed
if event.kind == MouseButtonPressed and not self.is_pressed:
self.on_pressed()
elif event.kind == MouseButtonReleased and self.is_pressed:
self.on_released()
else:
if event.kind == MouseButtonPressed:
if self.is_pressed: self.on_released()
else: self.on_pressed()
elif not self.is_toggle:
self.is_pressed = false
proc on_mouse_entered(self:BaseButton, emit = true) =
# echo "BaseButton: on_mouse_entered"
self.sort_colors()
if emit: self.emit_signal("mouse_entered")
proc on_mouse_exited(self:BaseButton, emit = true) =
# echo "BaseButton: on_mouse_exited"
if not self.is_toggle:
self.is_pressed = false
self.sort_colors()
if emit: self.emit_signal("mouse_exited")
proc on_pressed(self:BaseButton, emit = true) =
# echo "BaseButton._on_pressed"
self.is_pressed = true
self.sort_colors()
if emit:
if self.is_toggle: self.emit_signal("toggled")
self.emit_signal("pressed")
proc on_released(self:BaseButton, emit = true) =
# echo "BaseButton._on_released"
self.is_pressed = false
self.sort_colors()
if emit:
if self.is_toggle: self.emit_signal("toggled")
self.emit_signal("released")
proc `active=`*(self:BaseButton, enable:bool) =
if self.is_active != enable:
self.is_active = enable
self.sort_colors()
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")
type
Button* = ref object of BaseButton
str:string
icn:int
proc `$`*(self:Button):string =
fmt "Button({self.x}, {self.y} | {self.w}, {self.h})"
proc adjust_size(self:Button) =
if self.icn >= 0 and self.str != "": self.w = len(self.str)+2
elif self.icn >= 0: self.w = 1
elif self.str != "": self.w = len(self.str)
else: self.w = 0
template `text`*(self:Button):string = self.str
template `text=`*(self:Button, text:string) =
self.str = text
self.adjust_size()
template `icon`*(self:Button):string = self.icn
template `icon=`*(self:Button, icon:int) =
self.icn = icon
self.adjust_size()
proc super(self:Button, x, y:int, text:string, style = def_theme.button) =
self.BaseButton.super(x, y, style)
self.name = "Button"
self.h = 1
self.text = text
self.icon = -1
proc super(self:Button, x, y:int, text:string, icon:int, style = def_theme.button) =
self.BaseButton.super(x, y, style)
self.name = "Button"
self.h = 1
self.text = text
self.icon = icon
proc super(self:Button, x, y:int, icon:int, style = def_theme.button) =
self.BaseButton.super(x, y, style)
self.name = "Button"
self.h = 1
self.icon = icon
self.text = ""
method draw*(self:Button, terminal:Terminal) =
if self.visible:
let gpos = self.global_pos
terminal.rect(gpos.x, gpos.y, self.w, self.h, self.bg)
if self.icn >= 0:
terminal.put(gpos.x, gpos.y, self.icn, self.fg)
if self.str != "":
if unlikely(self.icn >= 0): terminal.print(gpos.x+2, gpos.y, self.str, self.fg)
else: terminal.print(gpos.x, gpos.y, self.str, self.fg)
# if self.icn >= 0 and self.str != "":
# terminal.put(gpos.x, gpos.y, self.icn, self.fg)
# terminal.print(gpos.x+2, gpos.y, self.str, self.fg)
# elif self.icn >= 0:
# terminal.put(gpos.x, gpos.y, self.icn, self.fg)
# elif self.str != "":
# terminal.print(gpos.x, gpos.y, self.str, self.fg)
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 =
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