├── .gitignore ├── src └── stui │ ├── kmloop.nims │ ├── controll.inc.nim │ ├── appbase │ ├── submodule.nim │ ├── intchannelutils.nim │ ├── appbase.nim │ ├── mainloop.inc.nim │ └── appbasetypes.nim │ ├── test_appmodule.nim │ ├── status.tss │ ├── colorsRGBto256.nim │ ├── theme.tss │ ├── terminal_extra.nim │ ├── uiext_maximize.nim │ ├── ui_splash.nim │ ├── test_rgb256.nim │ ├── ui_button.nim │ ├── colors_extra.nim │ ├── ui_progressbar.nim │ ├── ui_shdbutton.nim │ ├── kmloop.nim │ ├── ui_togglebutton.nim │ ├── ui_chooser.nim │ ├── ui_fineprogressbar.nim │ ├── ui_stringlistbox.nim │ ├── ui_selectbox.nim │ └── ui_textbox.nim ├── tests ├── deprecated │ ├── test1.nim │ └── test1.nims ├── template │ ├── stui_template │ ├── appbase │ │ ├── mainChannelString.inc.nim │ │ ├── myappbasetypes.nim │ │ ├── mainChannelInt.inc.nim │ │ ├── mainChannelIntChecked.inc.nim │ │ ├── logger.inc.nim │ │ ├── mainChannelIntTalkback.inc.nim │ │ └── mainChannelJson.inc.nim │ ├── stui │ │ ├── status.tss │ │ ├── linegraph.tss │ │ └── theme.tss │ ├── nim.cfg │ ├── channeltestmodule.nim │ └── stui_template.nim ├── splashtests │ └── string │ │ ├── stringsplash │ │ ├── stui │ │ ├── status.tss │ │ ├── linegraph.tss │ │ └── theme.tss │ │ └── stringsplash.nim ├── template_simpleapp │ ├── template_simpleapp │ ├── stui │ │ ├── status.tss │ │ ├── linegraph.tss │ │ └── theme.tss │ └── template_simpleapp.nim └── test_old │ ├── stui_test1.nims │ ├── stui │ ├── status.tss │ ├── linegraph.tss │ └── theme.tss │ └── mainloop.inc.nim ├── doc ├── LineGraph1.png ├── LineGraph2.png ├── Screenshot_2018-09-14_14-07-18.png ├── Screenshot_2018-09-14_14-07-41.png ├── Screenshot_2018-10-03_15-23-40.png ├── FileSelect_Screenshot_2018-10-20_13-35-40.png ├── Colors.md ├── Controlls.md └── appbase.md ├── stui.nimble ├── devtools ├── benchmark.nim ├── snippets.nim ├── colortsvparser.nim └── colorsRGB_enum ├── nim.cfg └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/nimcache 2 | -------------------------------------------------------------------------------- /src/stui/kmloop.nims: -------------------------------------------------------------------------------- 1 | --threads:on 2 | -------------------------------------------------------------------------------- /tests/deprecated/test1.nim: -------------------------------------------------------------------------------- 1 | doAssert(1 + 1 == 2) 2 | -------------------------------------------------------------------------------- /tests/deprecated/test1.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /doc/LineGraph1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/doc/LineGraph1.png -------------------------------------------------------------------------------- /doc/LineGraph2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/doc/LineGraph2.png -------------------------------------------------------------------------------- /tests/template/stui_template: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/tests/template/stui_template -------------------------------------------------------------------------------- /tests/splashtests/string/stringsplash: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/tests/splashtests/string/stringsplash -------------------------------------------------------------------------------- /doc/Screenshot_2018-09-14_14-07-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/doc/Screenshot_2018-09-14_14-07-18.png -------------------------------------------------------------------------------- /doc/Screenshot_2018-09-14_14-07-41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/doc/Screenshot_2018-09-14_14-07-41.png -------------------------------------------------------------------------------- /doc/Screenshot_2018-10-03_15-23-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/doc/Screenshot_2018-10-03_15-23-40.png -------------------------------------------------------------------------------- /tests/template_simpleapp/template_simpleapp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/tests/template_simpleapp/template_simpleapp -------------------------------------------------------------------------------- /doc/FileSelect_Screenshot_2018-10-20_13-35-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nais314/stui/HEAD/doc/FileSelect_Screenshot_2018-10-20_13-35-40.png -------------------------------------------------------------------------------- /src/stui/controll.inc.nim: -------------------------------------------------------------------------------- 1 | import stui, stui/[colors256, colors_extra] 2 | import terminal, terminal_extra, colors, unicode 3 | import tables, locks, parseutils, strutils, os, times, ospaths 4 | -------------------------------------------------------------------------------- /tests/test_old/stui_test1.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") 2 | --threads:on 3 | --deadCodeElim:on 4 | --app:console 5 | #--opt:speed 6 | #--debuginfo 7 | #--linedir:on 8 | #--gc:boehm 9 | #--gc:v2 10 | #--threadanalysis:off -------------------------------------------------------------------------------- /stui.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.20190515" 4 | author = "Istvan Nagy" 5 | description = "Simplified Terminal UI for ANSI terminals" 6 | license = "GPLv3" 7 | srcDir = "src" 8 | 9 | # Dependencies 10 | 11 | requires "nim >= 0.19.6" 12 | 13 | task test, "Runs the test suite": 14 | exec "nim c -r tests/stui_test1.nim" 15 | 16 | task testold, "Runs the test suite": 17 | exec "nim c -r tests/stui_template.nim" -------------------------------------------------------------------------------- /src/stui/appbase/submodule.nim: -------------------------------------------------------------------------------- 1 | # This is just an example to get you started. Users of your library will 2 | # import this file by writing ``import appcore/submodule``. Feel free to rename or 3 | # remove this file altogether. You may create additional modules alongside 4 | # this file as required. 5 | 6 | type 7 | Submodule* = object 8 | name*: string 9 | 10 | proc initSubmodule*(): Submodule = 11 | ## Initialises a new ``Submodule`` object. 12 | Submodule(name: "Anonymous") 13 | -------------------------------------------------------------------------------- /devtools/benchmark.nim: -------------------------------------------------------------------------------- 1 | # https://stackoverflow.com/questions/36577570/how-to-benchmark-few-lines-of-code-in-nim 2 | import times, os, strutils 3 | 4 | template benchmark(benchmarkName: string, code: untyped) = 5 | block: 6 | let t0 = epochTime() 7 | code 8 | let elapsed = epochTime() - t0 9 | let elapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 3) 10 | echo "CPU Time [", benchmarkName, "] ", elapsedStr, "s" 11 | 12 | benchmark "my benchmark": 13 | sleep 300 -------------------------------------------------------------------------------- /tests/template/appbase/mainChannelString.inc.nim: -------------------------------------------------------------------------------- 1 | var mainChannelString = getMcsChannel() 2 | mainChannelString[].open() 3 | 4 | proc handleMainChannelString()= 5 | var inbox = tryRecv( (mainChannelString[]) ) #tuple[dataAvailable: bool, msg: TMsg] 6 | if inbox.dataAvailable: 7 | case inbox.msg: 8 | of "hello": 9 | when defined(logger_enabled): debug " $$$ handleMainChannelString recived> " 10 | of "quit": 11 | app.quit() 12 | else: 13 | app.trigger(inbox.msg) 14 | -------------------------------------------------------------------------------- /tests/template/appbase/myappbasetypes.nim: -------------------------------------------------------------------------------- 1 | import stui/appbase/appbasetypes 2 | export appbasetypes 3 | 4 | #! CREATE INHERIED APP TYPE HERE, FOR USE 5 | import stui 6 | type 7 | AppFlags* = enum #! WRITE YOUR FLAGS 8 | ## appbase App has 12 int flags in .flags* 9 | quitFlag, 10 | debugFlag, 11 | threadsFlag, # threads counter for graceful quit 12 | scenarioFlag # eg maintenence 13 | 14 | MyApp* = stui.App 15 | 16 | #------------------------------------------------------------------------------- 17 | type 18 | InputLoopEvent* = KMEvent #! replace this with your type - now it is stuis KMEvent -------------------------------------------------------------------------------- /devtools/snippets.nim: -------------------------------------------------------------------------------- 1 | import stui, terminal, colors, colors_extra, colors256 unicode, tables 2 | 3 | 4 | 5 | proc drawit(this: Controll) = 6 | draw(Button(this)) 7 | 8 | proc focus(this: Controll)= 9 | this.activeStyle = this.styles["input:focus"] 10 | 11 | proc blur(this: Controll)= 12 | this.activeStyle = this.styles["input"] 13 | 14 | proc cancel(this: Controll)=discard 15 | 16 | 17 | proc onClick(this: Controll, event:KMEvent)=discard 18 | 19 | proc onDrag(this: Controll, event:KMEvent)=discard 20 | 21 | proc onDrop(this: Controll, event:KMEvent)=discard 22 | 23 | proc onKeyPress(this: Controll, event:KMEvent)=discard -------------------------------------------------------------------------------- /devtools/colortsvparser.nim: -------------------------------------------------------------------------------- 1 | import parsecsv 2 | 3 | var ofile: File 4 | 5 | ofile = open("ColorsX256",fmWrite) 6 | 7 | ofile.write("type Color256 = enum\n") 8 | 9 | type 10 | color256 = tuple[name: string, num: string] 11 | var 12 | c256: color256 13 | 14 | 15 | 16 | var p: CsvParser 17 | p.open("256colornames.tsv",'\t') 18 | while p.readRow(): 19 | var c:int = 0 20 | for val in items(p.row): 21 | if c <= 1: 22 | #echo "##", val, "##" 23 | if c == 0: 24 | c256.num = val 25 | else: 26 | c256.name = val 27 | c += 1 28 | echo c256 29 | ofile.write(" " & c256.name & " = " & c256.num & ",\n") 30 | 31 | p.close() 32 | 33 | ofile.close() -------------------------------------------------------------------------------- /src/stui/test_appmodule.nim: -------------------------------------------------------------------------------- 1 | import stui, os, threadpool 2 | 3 | 4 | 5 | proc channeltest(miso:ptr Channel[string]){.thread.}= 6 | for i in 0..4: 7 | discard trySend(miso[], "test1") 8 | #sleep(500) 9 | discard trySend(miso[], "test2") 10 | 11 | proc main*(app:App)= 12 | let win = app.activeWorkSpace.tiles[0].newWindow("appmodule test") 13 | app.redraw() 14 | 15 | 16 | discard trySend(app.itc[], "test1") 17 | 18 | app.activeWindow.setTitle("set label") 19 | 20 | #[ for i in 0..15: 21 | discard trySend(app.itc[], "test1") 22 | sleep(500) ]# 23 | 24 | var th : Thread[ptr Channel[string]] 25 | createThread[ptr Channel[string]](th, channeltest, app.itc) 26 | joinThread(th) 27 | -------------------------------------------------------------------------------- /src/stui/status.tss: -------------------------------------------------------------------------------- 1 | [error] 2 | bgColor16 = "bgRed" 3 | bgColor256 = "red" 4 | bgColorRGB = "red" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "White" 7 | fgColorRGB = "White" 8 | border = "none" 9 | 10 | [warning] 11 | bgColor16 = "bgYellow" 12 | bgColor256 = "orange" 13 | bgColorRGB = "orange" 14 | fgColor16 = "fgBlack" 15 | fgColor256 = "black" 16 | fgColorRGB = "black" 17 | border = "none" 18 | 19 | [info] 20 | bgColor16 = "bgBlue" 21 | bgColor256 = "blue" 22 | bgColorRGB = "blue" 23 | fgColor16 = "fgWhite" 24 | fgColor256 = "white" 25 | fgColorRGB = "white" 26 | border = "none" 27 | 28 | [success] 29 | bgColor16 = "bgGreen" 30 | bgColor256 = "green" 31 | bgColorRGB = "green" 32 | fgColor16 = "fgWhite" 33 | fgColor256 = "White" 34 | fgColorRGB = "White" 35 | border = "none" 36 | 37 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | --threads:on 2 | --deadCodeElim:on 3 | --nimcache:"/~/.nimcache" 4 | 5 | --define:logger_enabled 6 | --define:mainChannelLog_enabled 7 | 8 | #--define:debugInfo_enabled 9 | #--define:debugTrace_enabled 10 | #--define:debugWarn_enabled 11 | 12 | --define:inputEventLoop_enabled # main HID event loop 13 | --define:mainChannelInt_enabled 14 | --define:mainChannelString_enabled 15 | --define:mainChannelIntTalkback_enabled # sends ptr int, change int value to talk back 16 | --define:mainChannelIntChecked_enabled # sends int and ptr Channel[int] to talk back 17 | #--define:mainChannelStringChecked_enabled # sends string and Channel[string] not impl. 18 | --define:mainChannelJsonChecked_enabled # aka InterCom 19 | --define:timedActions_enabled 20 | #--define:mainFlags_enabled # not implemented 21 | -------------------------------------------------------------------------------- /tests/template/stui/status.tss: -------------------------------------------------------------------------------- 1 | [error] 2 | bgColor16 = "bgRed" 3 | bgColor256 = "red" 4 | bgColorRGB = "red" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "White" 7 | fgColorRGB = "White" 8 | border = "none" 9 | 10 | [warning] 11 | bgColor16 = "bgYellow" 12 | bgColor256 = "orange" 13 | bgColorRGB = "orange" 14 | fgColor16 = "fgBlack" 15 | fgColor256 = "black" 16 | fgColorRGB = "black" 17 | border = "none" 18 | 19 | [info] 20 | bgColor16 = "bgBlue" 21 | bgColor256 = "blue" 22 | bgColorRGB = "blue" 23 | fgColor16 = "fgWhite" 24 | fgColor256 = "white" 25 | fgColorRGB = "white" 26 | border = "none" 27 | 28 | [success] 29 | bgColor16 = "bgGreen" 30 | bgColor256 = "green" 31 | bgColorRGB = "green" 32 | fgColor16 = "fgWhite" 33 | fgColor256 = "White" 34 | fgColorRGB = "White" 35 | border = "none" 36 | 37 | -------------------------------------------------------------------------------- /tests/test_old/stui/status.tss: -------------------------------------------------------------------------------- 1 | [error] 2 | bgColor16 = "bgRed" 3 | bgColor256 = "red" 4 | bgColorRGB = "red" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "White" 7 | fgColorRGB = "White" 8 | border = "none" 9 | 10 | [warning] 11 | bgColor16 = "bgYellow" 12 | bgColor256 = "orange" 13 | bgColorRGB = "orange" 14 | fgColor16 = "fgBlack" 15 | fgColor256 = "black" 16 | fgColorRGB = "black" 17 | border = "none" 18 | 19 | [info] 20 | bgColor16 = "bgBlue" 21 | bgColor256 = "blue" 22 | bgColorRGB = "blue" 23 | fgColor16 = "fgWhite" 24 | fgColor256 = "white" 25 | fgColorRGB = "white" 26 | border = "none" 27 | 28 | [success] 29 | bgColor16 = "bgGreen" 30 | bgColor256 = "green" 31 | bgColorRGB = "green" 32 | fgColor16 = "fgWhite" 33 | fgColor256 = "White" 34 | fgColorRGB = "White" 35 | border = "none" 36 | 37 | -------------------------------------------------------------------------------- /tests/splashtests/string/stui/status.tss: -------------------------------------------------------------------------------- 1 | [error] 2 | bgColor16 = "bgRed" 3 | bgColor256 = "red" 4 | bgColorRGB = "red" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "White" 7 | fgColorRGB = "White" 8 | border = "none" 9 | 10 | [warning] 11 | bgColor16 = "bgYellow" 12 | bgColor256 = "orange" 13 | bgColorRGB = "orange" 14 | fgColor16 = "fgBlack" 15 | fgColor256 = "black" 16 | fgColorRGB = "black" 17 | border = "none" 18 | 19 | [info] 20 | bgColor16 = "bgBlue" 21 | bgColor256 = "blue" 22 | bgColorRGB = "blue" 23 | fgColor16 = "fgWhite" 24 | fgColor256 = "white" 25 | fgColorRGB = "white" 26 | border = "none" 27 | 28 | [success] 29 | bgColor16 = "bgGreen" 30 | bgColor256 = "green" 31 | bgColorRGB = "green" 32 | fgColor16 = "fgWhite" 33 | fgColor256 = "White" 34 | fgColorRGB = "White" 35 | border = "none" 36 | 37 | -------------------------------------------------------------------------------- /tests/template_simpleapp/stui/status.tss: -------------------------------------------------------------------------------- 1 | [error] 2 | bgColor16 = "bgRed" 3 | bgColor256 = "red" 4 | bgColorRGB = "red" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "White" 7 | fgColorRGB = "White" 8 | border = "none" 9 | 10 | [warning] 11 | bgColor16 = "bgYellow" 12 | bgColor256 = "orange" 13 | bgColorRGB = "orange" 14 | fgColor16 = "fgBlack" 15 | fgColor256 = "black" 16 | fgColorRGB = "black" 17 | border = "none" 18 | 19 | [info] 20 | bgColor16 = "bgBlue" 21 | bgColor256 = "blue" 22 | bgColorRGB = "blue" 23 | fgColor16 = "fgWhite" 24 | fgColor256 = "white" 25 | fgColorRGB = "white" 26 | border = "none" 27 | 28 | [success] 29 | bgColor16 = "bgGreen" 30 | bgColor256 = "green" 31 | bgColorRGB = "green" 32 | fgColor16 = "fgWhite" 33 | fgColor256 = "White" 34 | fgColorRGB = "White" 35 | border = "none" 36 | 37 | -------------------------------------------------------------------------------- /tests/template/nim.cfg: -------------------------------------------------------------------------------- 1 | --threads:on 2 | --deadCodeElim:on 3 | --nimcache:"/~/.nimcache" 4 | #--showAllMismatches 5 | 6 | --define:logger_enabled 7 | --define:mainChannelLog_enabled 8 | 9 | #--define:debugInfo_enabled 10 | #--define:debugTrace_enabled 11 | #--define:debugWarn_enabled 12 | 13 | --define:inputEventLoop_enabled # main HID event loop 14 | --define:mainChannelInt_enabled 15 | --define:mainChannelString_enabled 16 | --define:mainChannelIntTalkback_enabled # sends ptr int, change int value to talk back 17 | --define:mainChannelIntChecked_enabled # sends int and ptr Channel[int] to talk back 18 | #--define:mainChannelStringChecked_enabled # sends string and Channel[string] not impl. 19 | --define:mainChannelJsonChecked_enabled # aka InterCom 20 | --define:timedActions_enabled 21 | #--define:mainFlags_enabled # not implemented 22 | -------------------------------------------------------------------------------- /tests/template/appbase/mainChannelInt.inc.nim: -------------------------------------------------------------------------------- 1 | ## the simplest channel - int. 2 | ## deeply copied, fire and forget ;) 3 | ## 4 | ## import appbase/intchannelutils 5 | ## var msg = unpackIntMsg(inbox.msg.uint) returns an array[0..3, uint] 6 | ## so, you have a 4 level message tree if you like/see so 7 | ## like app functions (1), shop functions (11), shop fun category (x), shop fun (243) 8 | ## levels based on int size, see src 9 | 10 | var mainChannelInt = getMciChannel() 11 | mainChannelInt[].open() 12 | 13 | type mainChannelIntCodes = enum 14 | mciHello = 1, 15 | mciQuit = int.high 16 | 17 | proc handleMainChannelInt()= 18 | var inbox = tryRecv( (mainChannelInt[]) ) #tuple[dataAvailable: bool, msg: TMsg] 19 | if inbox.dataAvailable: 20 | case inbox.msg: 21 | of 1: #"redraw": 22 | when defined(logger_enabled): debug "+++++++ Hello MainChannelInt!" 23 | discard 24 | of int(mciQuit): #"quit": 25 | app.quit() 26 | else: 27 | app.trigger($ inbox.msg) 28 | -------------------------------------------------------------------------------- /tests/template/appbase/mainChannelIntChecked.inc.nim: -------------------------------------------------------------------------------- 1 | type 2 | #McicChannel* = Channel[tuple[val:int,chan:ptr Channel[int]]] 3 | MainChannelIntCheckedCodes* = enum #! OVERWRITE THIS 4 | mcicHello = 1, 5 | mcicQuit = int.high 6 | 7 | var mainChannelIntChecked = getMcicChannel() 8 | mainChannelIntChecked[].open() 9 | proc handleMainChannelIntChecked()= #! OVERWRITE THIS 10 | var inbox = tryRecv( (mainChannelIntChecked[]) ) #tuple[dataAvailable: bool, msg: TMsg] 11 | if inbox.dataAvailable: 12 | inbox.msg.chan[].open() 13 | when defined(logger_enabled): debug "::: handleMainChannelIntChecked recv> ", inbox.msg.val 14 | case inbox.msg.val: 15 | of 1: 16 | when defined(logger_enabled): debug "::: Hello MainChannelIntChecked!" 17 | inbox.msg.chan[].send(4) 18 | 19 | of int(mcicQuit): #"quit": 20 | app.quit() 21 | 22 | else: 23 | when defined(logger_enabled): debug "::: handleMainChannelIntChecked> else" 24 | app.trigger($ inbox.msg.val) 25 | -------------------------------------------------------------------------------- /doc/Colors.md: -------------------------------------------------------------------------------- 1 | see colors_extra.nim, colors256.nim, colorsRGBto256.nim 2 | 3 | for now 8 and 16 color modes use the same procs, 4 | 256 color and rgb color modes are widely available. 5 | 6 | colorsRGBto256.nim makes using default nim/x11 color names 7 | for 256 + rgb color modes available - useful for themeing. 8 | see colors.nim stdlib for color names too. 9 | 10 | 11 | from nim 0.19 12 | setting colors are handled by ANSI escape sequences, 13 | for GC safety, see: colors_extra.setForegroundColor 14 | 15 | if you use the standard 8/16 colors, its appereance will vary depending on the terminal emulator you use :) 16 | "color modes are backward compatible": on 256 colors terminal you can use 16 color; on RGB you can use every method, even mixed =) 17 | 18 | 19 | **Styles** 20 | 21 | to spare repeating setMargin("bottom", 1), modify app's styles before creating 22 | controll instances. 23 | 24 | "input" is the default style, suits TextBox and such, background white 25 | "input:inverse" when the foreground color is white - see ProgressBar -------------------------------------------------------------------------------- /tests/template/stui/linegraph.tss: -------------------------------------------------------------------------------- 1 | [mark-positive] 2 | bgColor16 = "bgBlack" 3 | bgColor256 = "black" 4 | bgColorRGB = "black" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "green" 7 | fgColorRGB = "green" 8 | border = "none" 9 | 10 | [mark-negative] 11 | bgColor16 = "bgBlack" 12 | bgColor256 = "black" 13 | bgColorRGB = "black" 14 | fgColor16 = "fgBlue" 15 | fgColor256 = "royalblue" 16 | fgColorRGB = "royalblue" 17 | border = "none" 18 | 19 | [graph-positive] 20 | bgColor16 = "bgBlack" 21 | bgColor256 = "black" 22 | bgColorRGB = "black" 23 | fgColor16 = "fgGreen" 24 | fgColor256 = "lime" 25 | fgColorRGB = "lime" 26 | border = "none" 27 | 28 | [graph-negative] 29 | bgColor16 = "bgBlack" 30 | bgColor256 = "black" 31 | bgColorRGB = "black" 32 | fgColor16 = "fgCyan" 33 | fgColor256 = "dodgerblue" 34 | fgColorRGB = "dodgerblue" 35 | border = "none" 36 | 37 | 38 | [graph-selected] 39 | bgColor16 = "bgBlack" 40 | bgColor256 = "black" 41 | bgColorRGB = "black" 42 | fgColor16 = "fgYellow" 43 | fgColor256 = "orange" 44 | fgColorRGB = "orange" 45 | border = "none" 46 | 47 | -------------------------------------------------------------------------------- /tests/test_old/stui/linegraph.tss: -------------------------------------------------------------------------------- 1 | [mark-positive] 2 | bgColor16 = "bgBlack" 3 | bgColor256 = "black" 4 | bgColorRGB = "black" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "green" 7 | fgColorRGB = "green" 8 | border = "none" 9 | 10 | [mark-negative] 11 | bgColor16 = "bgBlack" 12 | bgColor256 = "black" 13 | bgColorRGB = "black" 14 | fgColor16 = "fgBlue" 15 | fgColor256 = "royalblue" 16 | fgColorRGB = "royalblue" 17 | border = "none" 18 | 19 | [graph-positive] 20 | bgColor16 = "bgBlack" 21 | bgColor256 = "black" 22 | bgColorRGB = "black" 23 | fgColor16 = "fgGreen" 24 | fgColor256 = "lime" 25 | fgColorRGB = "lime" 26 | border = "none" 27 | 28 | [graph-negative] 29 | bgColor16 = "bgBlack" 30 | bgColor256 = "black" 31 | bgColorRGB = "black" 32 | fgColor16 = "fgCyan" 33 | fgColor256 = "dodgerblue" 34 | fgColorRGB = "dodgerblue" 35 | border = "none" 36 | 37 | 38 | [graph-selected] 39 | bgColor16 = "bgBlack" 40 | bgColor256 = "black" 41 | bgColorRGB = "black" 42 | fgColor16 = "fgYellow" 43 | fgColor256 = "orange" 44 | fgColorRGB = "orange" 45 | border = "none" 46 | 47 | -------------------------------------------------------------------------------- /tests/splashtests/string/stui/linegraph.tss: -------------------------------------------------------------------------------- 1 | [mark-positive] 2 | bgColor16 = "bgBlack" 3 | bgColor256 = "black" 4 | bgColorRGB = "black" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "green" 7 | fgColorRGB = "green" 8 | border = "none" 9 | 10 | [mark-negative] 11 | bgColor16 = "bgBlack" 12 | bgColor256 = "black" 13 | bgColorRGB = "black" 14 | fgColor16 = "fgBlue" 15 | fgColor256 = "royalblue" 16 | fgColorRGB = "royalblue" 17 | border = "none" 18 | 19 | [graph-positive] 20 | bgColor16 = "bgBlack" 21 | bgColor256 = "black" 22 | bgColorRGB = "black" 23 | fgColor16 = "fgGreen" 24 | fgColor256 = "lime" 25 | fgColorRGB = "lime" 26 | border = "none" 27 | 28 | [graph-negative] 29 | bgColor16 = "bgBlack" 30 | bgColor256 = "black" 31 | bgColorRGB = "black" 32 | fgColor16 = "fgCyan" 33 | fgColor256 = "dodgerblue" 34 | fgColorRGB = "dodgerblue" 35 | border = "none" 36 | 37 | 38 | [graph-selected] 39 | bgColor16 = "bgBlack" 40 | bgColor256 = "black" 41 | bgColorRGB = "black" 42 | fgColor16 = "fgYellow" 43 | fgColor256 = "orange" 44 | fgColorRGB = "orange" 45 | border = "none" 46 | 47 | -------------------------------------------------------------------------------- /tests/template_simpleapp/stui/linegraph.tss: -------------------------------------------------------------------------------- 1 | [mark-positive] 2 | bgColor16 = "bgBlack" 3 | bgColor256 = "black" 4 | bgColorRGB = "black" 5 | fgColor16 = "fgWhite" 6 | fgColor256 = "green" 7 | fgColorRGB = "green" 8 | border = "none" 9 | 10 | [mark-negative] 11 | bgColor16 = "bgBlack" 12 | bgColor256 = "black" 13 | bgColorRGB = "black" 14 | fgColor16 = "fgBlue" 15 | fgColor256 = "royalblue" 16 | fgColorRGB = "royalblue" 17 | border = "none" 18 | 19 | [graph-positive] 20 | bgColor16 = "bgBlack" 21 | bgColor256 = "black" 22 | bgColorRGB = "black" 23 | fgColor16 = "fgGreen" 24 | fgColor256 = "lime" 25 | fgColorRGB = "lime" 26 | border = "none" 27 | 28 | [graph-negative] 29 | bgColor16 = "bgBlack" 30 | bgColor256 = "black" 31 | bgColorRGB = "black" 32 | fgColor16 = "fgCyan" 33 | fgColor256 = "dodgerblue" 34 | fgColorRGB = "dodgerblue" 35 | border = "none" 36 | 37 | 38 | [graph-selected] 39 | bgColor16 = "bgBlack" 40 | bgColor256 = "black" 41 | bgColorRGB = "black" 42 | fgColor16 = "fgYellow" 43 | fgColor256 = "orange" 44 | fgColorRGB = "orange" 45 | border = "none" 46 | 47 | -------------------------------------------------------------------------------- /src/stui/appbase/intchannelutils.nim: -------------------------------------------------------------------------------- 1 | when isMainModule: import strutils # debug echo string format 2 | 3 | when sizeof(int) >= 8: 4 | const PackedIntMsgBits* = [16,16,16,16] 5 | 6 | when sizeof(int) == 4: 7 | const PackedIntMsgBits* = [8,8,16,0] #256, 256, 65536, 0 8 | 9 | type 10 | PackedIntMsg* = array[0..3, uint] 11 | 12 | 13 | proc packIntMsg*(pc: PackedIntMsg):uint= 14 | result = 0 15 | for i in 0..3: 16 | result = result or pc[i] 17 | when isMainModule: echo toBin(result.int,sizeof(uint)*8), " - ", toBin(pc[i].int, sizeof(uint)*8) 18 | if i < 3 : result = result shl PackedIntMsgBits[i] 19 | 20 | 21 | proc unpackIntMsg*(pc: uint):PackedIntMsg= 22 | var bitshift:int 23 | var c = 3 # reverse array 24 | for i in 0..3: #countdown(3,0): 25 | bitshift = 0 26 | for c in 0.. getMcitChannel() 10 | ## 11 | ## import appbase/intchannelutils 12 | ## var msg = unpackIntMsg(inbox.msg[].uint) returns an array[0..3, uint] 13 | ## so, you have a 4 level message tree if you like/see so 14 | ## like app functions (1), shop functions (11), shop fun category (x), shop fun (243) 15 | ## levels based on int size, see src 16 | 17 | 18 | var mainChannelIntTalkback = getMcitChannel() 19 | mainChannelIntTalkback[].open() 20 | 21 | import appbase/intchannelutils 22 | 23 | proc handleMainChannelIntTalkback()= #! OVERWRITE THIS 24 | #when defined(logger_enabled): debug "---=== mainChannelIntTalkback[].peek()> ", mainChannelIntTalkback[].peek() 25 | var inbox = tryRecv( (mainChannelIntTalkback[]) ) #tuple[dataAvailable: bool, msg: TMsg] 26 | if inbox.dataAvailable: 27 | case inbox.msg[]: 28 | of 1: 29 | when defined(logger_enabled): debug "---=== Hello handleMainChannelIntTalkback!" 30 | atomicInc(inbox.msg[]) 31 | 32 | of 256..int.high: 33 | var msg = unpackIntMsg(inbox.msg[].uint) 34 | when defined(logger_enabled): 35 | debug "---=== handleMainChannelIntTalkback unpackIntMsg msg> ", msg 36 | atomicInc(inbox.msg[]) 37 | 38 | else: 39 | when defined(logger_enabled): debug "---=== handleMainChannelIntTalkback> else" 40 | app.trigger($ inbox.msg[]) 41 | atomicInc(inbox.msg[]) 42 | -------------------------------------------------------------------------------- /src/stui/colorsRGBto256.nim: -------------------------------------------------------------------------------- 1 | const colorNamesRGBto256* = [ 2 | ("aliceblue", 195), 3 | ("antiquewhite", 223), 4 | ("aquamarine", 49), 5 | ("azure", 195), 6 | ("beige", 229), 7 | ("bisque", 216), 8 | ("blanchedalmond", 223), 9 | ("brown", 124), 10 | ("burlywood", 173), 11 | ("chocolate", 130), 12 | ("coral", 131), 13 | ("crimson", 124), 14 | ("darkgray", 242), 15 | ("darkolivegreen", 22), 16 | ("darkorchid", 92), 17 | ("darksalmon", 209), 18 | ("darkslateblue", 62), 19 | ("darkslategray", 60), 20 | ("deeppink", 163), 21 | ("dimgray", 238), 22 | ("firebrick", 88), 23 | ("floralwhite", 230), 24 | ("forestgreen", 28), 25 | ("gainsboro", 251), 26 | ("ghostwhite", 254), 27 | ("goldenrod", 172), 28 | ("gray", 247), 29 | ("indigo", 17), 30 | ("ivory", 230), 31 | ("lavender", 189), 32 | ("lavenderblush", 225), 33 | ("lawngreen", 154), 34 | ("lemonchiffon", 229), 35 | ("lightblue", 74), 36 | ("lightgoldenrodyellow", 187), 37 | ("lightgrey", 249), 38 | ("lightskyblue", 117), 39 | ("lightslategray", 103), 40 | ("limegreen", 70), 41 | ("linen", 187), 42 | ("mediumaquamarine", 79), 43 | ("mediumblue", 20), 44 | ("mediumseagreen", 29), 45 | ("mediumslateblue", 61), 46 | ("midnightblue", 17), 47 | ("mintcream", 193), 48 | ("mistyrose", 217), 49 | ("moccasin", 223), 50 | ("oldlace", 230), 51 | ("olivedrab", 107), 52 | ("palegoldenrod", 222), 53 | ("paleturquoise", 122), 54 | ("palevioletred", 131), 55 | ("papayawhip", 229), 56 | ("peachpuff", 223), 57 | ("peru", 208), 58 | ("powderblue", 159), 59 | ("saddlebrown", 3), 60 | ("seashell", 230), 61 | ("sienna", 3), 62 | ("slategray", 23), 63 | ("snow", 254), 64 | ("tomato", 9), 65 | ("violet", 177), 66 | ("whitesmoke", 254) ] 67 | -------------------------------------------------------------------------------- /tests/template/appbase/mainChannelJson.inc.nim: -------------------------------------------------------------------------------- 1 | # the beginning is might verbose a bit, but its now reusable in submodules 2 | # with adjusting the thread id and channel 3 | var interCom = getIntercom() 4 | let myThId = 0 #getThreadId() # main thread is thread 0 5 | interCom.addFeed(0) # init main thread - 0 - channels 6 | interCom[0][myThId] = getMcjChannel() # main thread uses the pre declared json channel 7 | open interCom[0][myThId][] 8 | 9 | proc handleMainChannelJsonChecked()= #! OVERWRITE THIS 10 | #when defined(logger_enabled): debug ">>>>>> handleMainChannelJsonChecked" 11 | open interCom[0][myThId][] 12 | 13 | var inbox = tryRecv( interCom[0][myThId][] ) #tuple[dataAvailable: bool, msg: TMsg] 14 | if inbox.dataAvailable: 15 | debug inbox.msg 16 | 17 | var jsonNode = parseJson(inbox.msg) 18 | let msgtyp = jsonNode["typ"].getInt() # [] returns exception if not set! 19 | let msgfrom = jsonNode{"from"}.getInt() # {} returns default if not set. 20 | 21 | ## case msgtyp: 22 | ## recommendation: 0: test, 1-999 system preserved, 1000- applications fun 23 | 24 | case msgtyp: #! OVERWRITE WITH YOUR OWN FUN 25 | of 101: #! add a channel to intercom - a thread registers in intercom 26 | when defined(logger_enabled): debug ">>>>>> adding new thread comm channel \n", inbox.msg 27 | if msgfrom != myThId: # main channel is pre registered 28 | var feedname = jsonNode["feed"].getInt() 29 | 30 | if not interCom.hasKey(feedname): 31 | interCom.addFeed(feedname) 32 | 33 | var chan = jsonNode["chanPtr"].getChannelJson() #cast[ptr ChannelJson](cast[uint](jsonNode["chanPtr"].getInt()))[] 34 | 35 | open chan[] # [] -> chan is ref, needs [] to get the Channel 36 | interCom[feedname].subscribe( msgfrom, chan ) 37 | when defined(logger_enabled): debug ">>>>>> ", interCom.hasKey(0) 38 | interCom[feedname].sendTo(msgfrom, """{ "r": 0 }""") 39 | 40 | of 0: 41 | when defined(logger_enabled): debug ">>>>>> hello from json channel" 42 | if msgfrom != myThId: # do not send to yourself - deadlock 43 | interCom[0].sendTo(msgfrom, """{ "r": 42 }""") # result=42 44 | when defined(logger_enabled): debug ">>>>>> reply sent...", msgfrom 45 | of 1: 46 | debug "THE END" 47 | app.quit() 48 | 49 | else: discard 50 | 51 | sleep(0) # cpuRelax -------------------------------------------------------------------------------- /src/stui/theme.tss: -------------------------------------------------------------------------------- 1 | # proc setTextStyle*(style: StyleSheetRef, textStyleStr: string)= 2 | # case textStyleStr: 3 | # of "": discard 4 | # of "styleBright", "styleBold": style.textStyle.incl(styleBright) 5 | # of "styleDim": style.textStyle.incl(styleDim) 6 | # of "styleUnknown", "styleItalic", "styleStandout": style.textStyle.incl(styleUnknown) 7 | # of "styleUnderscore", "styleUnderline": style.textStyle.incl(styleUnderscore) 8 | # of "styleBlink": style.textStyle.incl(styleBlink) 9 | # of "styleReverse", "styleInverse": style.textStyle.incl(styleReverse) 10 | # of "styleHidden": style.textStyle.incl(styleHidden) 11 | 12 | [window] 13 | bgColor16 = "bgBlue" 14 | bgColor256 = "MidnightBlue" 15 | bgColorRGB = "MidnightBlue" 16 | fgColor16 = "fgWhite" 17 | fgColor256 = "White" 18 | fgColorRGB = "White" 19 | border = "none" 20 | 21 | [dock] 22 | bgColor16 = "bgBlack" 23 | bgColor256 = "Grey3" 24 | bgColorRGB = "48,48,42" 25 | fgColor16 = "fgWhite" 26 | fgColor256 = "White" 27 | fgColorRGB = "White" 28 | border = "none" 29 | 30 | 31 | [input] 32 | bgColor16 = 47 33 | bgColor256 = 254 34 | bgColorRGB = "255,255,255" 35 | fgColor16 = 30 36 | fgColor256 = 0 37 | fgColorRGB = "black" 38 | #"0,0,135" 39 | #textStyle = "styleBright" 40 | border = "none" 41 | 42 | [input-focus] 43 | bgColor16 = 43 44 | bgColor256 = 222 45 | bgColorRGB = "255,215,95" 46 | fgColor16 = 30 47 | fgColor256 = 0 48 | fgColorRGB = "0,0,135" 49 | textStyle = "styleUnknown" 50 | border = "none" 51 | 52 | [input-drag] 53 | bgColor16 = 44 54 | bgColor256 = 128 55 | bgColorRGB = "175,0,215" 56 | fgColor16 = 30 57 | fgColor256 = 0 58 | fgColorRGB = "0,0,135" 59 | textStyle = "styleBlink" 60 | border = "none" 61 | 62 | [input-disabled] 63 | bgColor16 = 40 64 | bgColor256 = 245 65 | bgColorRGB = "100,100,100" 66 | fgColor16 = 37 67 | fgColor256 = 90 68 | fgColorRGB = "222,222,222" 69 | border = "none" 70 | 71 | [input-even] 72 | bgColor16 = 40 73 | bgColor256 = 237 74 | bgColorRGB = "gainsboro" 75 | fgColor16 = 30 76 | fgColor256 = 0 77 | fgColorRGB = "black" 78 | border = "none" 79 | 80 | [input-odd] 81 | bgColor16 = 40 82 | bgColor256 = 235 83 | bgColorRGB = "ghostwhite" 84 | fgColor16 = 30 85 | fgColor256 = 0 86 | fgColorRGB = "black" 87 | border = "none" 88 | 89 | [input-even_dark] 90 | bgColor16 = 37 91 | bgColor256 = 237 92 | bgColorRGB = "0,0,0" 93 | fgColor16 = 30 94 | fgColor256 = "white" 95 | fgColorRGB = "white" 96 | border = "none" 97 | 98 | [input-odd_dark] 99 | bgColor16 = 37 100 | bgColor256 = 235 101 | bgColorRGB = "25,25,25" 102 | fgColor16 = 30 103 | fgColor256 = "white" 104 | fgColorRGB = "white" 105 | border = "none" -------------------------------------------------------------------------------- /tests/template/stui/theme.tss: -------------------------------------------------------------------------------- 1 | # proc setTextStyle*(style: StyleSheetRef, textStyleStr: string)= 2 | # case textStyleStr: 3 | # of "": discard 4 | # of "styleBright", "styleBold": style.textStyle.incl(styleBright) 5 | # of "styleDim": style.textStyle.incl(styleDim) 6 | # of "styleUnknown", "styleItalic", "styleStandout": style.textStyle.incl(styleUnknown) 7 | # of "styleUnderscore", "styleUnderline": style.textStyle.incl(styleUnderscore) 8 | # of "styleBlink": style.textStyle.incl(styleBlink) 9 | # of "styleReverse", "styleInverse": style.textStyle.incl(styleReverse) 10 | # of "styleHidden": style.textStyle.incl(styleHidden) 11 | 12 | [window] 13 | bgColor16 = "bgBlue" 14 | bgColor256 = "MidnightBlue" 15 | bgColorRGB = "MidnightBlue" 16 | fgColor16 = "fgWhite" 17 | fgColor256 = "White" 18 | fgColorRGB = "White" 19 | border = "none" 20 | 21 | [dock] 22 | bgColor16 = "bgBlack" 23 | bgColor256 = "Grey3" 24 | bgColorRGB = "48,48,42" 25 | fgColor16 = "fgWhite" 26 | fgColor256 = "White" 27 | fgColorRGB = "White" 28 | border = "none" 29 | 30 | 31 | [input] 32 | bgColor16 = 47 33 | bgColor256 = 254 34 | bgColorRGB = "255,255,255" 35 | fgColor16 = 30 36 | fgColor256 = 0 37 | fgColorRGB = "black" 38 | #"0,0,135" 39 | #textStyle = "styleBright" 40 | border = "none" 41 | 42 | [input-focus] 43 | bgColor16 = 43 44 | bgColor256 = 222 45 | bgColorRGB = "255,215,95" 46 | fgColor16 = 30 47 | fgColor256 = 0 48 | fgColorRGB = "0,0,135" 49 | textStyle = "styleUnknown" 50 | border = "none" 51 | 52 | [input-drag] 53 | bgColor16 = 44 54 | bgColor256 = 128 55 | bgColorRGB = "175,0,215" 56 | fgColor16 = 30 57 | fgColor256 = 0 58 | fgColorRGB = "0,0,135" 59 | textStyle = "styleBlink" 60 | border = "none" 61 | 62 | [input-disabled] 63 | bgColor16 = 40 64 | bgColor256 = 245 65 | bgColorRGB = "100,100,100" 66 | fgColor16 = 37 67 | fgColor256 = 90 68 | fgColorRGB = "222,222,222" 69 | border = "none" 70 | 71 | 72 | 73 | [input-even] 74 | bgColor16 = 40 75 | bgColor256 = 237 76 | bgColorRGB = "gainsboro" 77 | fgColor16 = 30 78 | fgColor256 = 0 79 | fgColorRGB = "black" 80 | border = "none" 81 | 82 | [input-odd] 83 | bgColor16 = 40 84 | bgColor256 = 235 85 | bgColorRGB = "ghostwhite" 86 | fgColor16 = 30 87 | fgColor256 = 0 88 | fgColorRGB = "black" 89 | border = "none" 90 | 91 | [input-even_dark] 92 | bgColor16 = 37 93 | bgColor256 = 237 94 | bgColorRGB = "0,0,0" 95 | fgColor16 = 30 96 | fgColor256 = "white" 97 | fgColorRGB = "white" 98 | border = "none" 99 | 100 | [input-odd_dark] 101 | bgColor16 = 37 102 | bgColor256 = 235 103 | bgColorRGB = "25,25,25" 104 | fgColor16 = 30 105 | fgColor256 = "white" 106 | fgColorRGB = "white" 107 | border = "none" -------------------------------------------------------------------------------- /tests/test_old/stui/theme.tss: -------------------------------------------------------------------------------- 1 | # proc setTextStyle*(style: StyleSheetRef, textStyleStr: string)= 2 | # case textStyleStr: 3 | # of "": discard 4 | # of "styleBright", "styleBold": style.textStyle.incl(styleBright) 5 | # of "styleDim": style.textStyle.incl(styleDim) 6 | # of "styleUnknown", "styleItalic", "styleStandout": style.textStyle.incl(styleUnknown) 7 | # of "styleUnderscore", "styleUnderline": style.textStyle.incl(styleUnderscore) 8 | # of "styleBlink": style.textStyle.incl(styleBlink) 9 | # of "styleReverse", "styleInverse": style.textStyle.incl(styleReverse) 10 | # of "styleHidden": style.textStyle.incl(styleHidden) 11 | 12 | [window] 13 | bgColor16 = "bgBlue" 14 | bgColor256 = "MidnightBlue" 15 | bgColorRGB = "MidnightBlue" 16 | fgColor16 = "fgWhite" 17 | fgColor256 = "White" 18 | fgColorRGB = "White" 19 | border = "none" 20 | 21 | [dock] 22 | bgColor16 = "bgBlack" 23 | bgColor256 = "Grey3" 24 | bgColorRGB = "48,48,42" 25 | fgColor16 = "fgWhite" 26 | fgColor256 = "White" 27 | fgColorRGB = "White" 28 | border = "none" 29 | 30 | 31 | [input] 32 | bgColor16 = 47 33 | bgColor256 = 254 34 | bgColorRGB = "255,255,255" 35 | fgColor16 = 30 36 | fgColor256 = 0 37 | fgColorRGB = "black" 38 | #"0,0,135" 39 | #textStyle = "styleBright" 40 | border = "none" 41 | 42 | [input-focus] 43 | bgColor16 = 43 44 | bgColor256 = 222 45 | bgColorRGB = "255,215,95" 46 | fgColor16 = 30 47 | fgColor256 = 0 48 | fgColorRGB = "0,0,135" 49 | textStyle = "styleUnknown" 50 | border = "none" 51 | 52 | [input-drag] 53 | bgColor16 = 44 54 | bgColor256 = 128 55 | bgColorRGB = "175,0,215" 56 | fgColor16 = 30 57 | fgColor256 = 0 58 | fgColorRGB = "0,0,135" 59 | textStyle = "styleBlink" 60 | border = "none" 61 | 62 | [input-disabled] 63 | bgColor16 = 40 64 | bgColor256 = 245 65 | bgColorRGB = "100,100,100" 66 | fgColor16 = 37 67 | fgColor256 = 90 68 | fgColorRGB = "222,222,222" 69 | border = "none" 70 | 71 | 72 | 73 | [input-even] 74 | bgColor16 = 40 75 | bgColor256 = 237 76 | bgColorRGB = "gainsboro" 77 | fgColor16 = 30 78 | fgColor256 = 0 79 | fgColorRGB = "black" 80 | border = "none" 81 | 82 | [input-odd] 83 | bgColor16 = 40 84 | bgColor256 = 235 85 | bgColorRGB = "ghostwhite" 86 | fgColor16 = 30 87 | fgColor256 = 0 88 | fgColorRGB = "black" 89 | border = "none" 90 | 91 | [input-even_dark] 92 | bgColor16 = 37 93 | bgColor256 = 237 94 | bgColorRGB = "0,0,0" 95 | fgColor16 = 30 96 | fgColor256 = "white" 97 | fgColorRGB = "white" 98 | border = "none" 99 | 100 | [input-odd_dark] 101 | bgColor16 = 37 102 | bgColor256 = 235 103 | bgColorRGB = "25,25,25" 104 | fgColor16 = 30 105 | fgColor256 = "white" 106 | fgColorRGB = "white" 107 | border = "none" -------------------------------------------------------------------------------- /tests/splashtests/string/stui/theme.tss: -------------------------------------------------------------------------------- 1 | # proc setTextStyle*(style: StyleSheetRef, textStyleStr: string)= 2 | # case textStyleStr: 3 | # of "": discard 4 | # of "styleBright", "styleBold": style.textStyle.incl(styleBright) 5 | # of "styleDim": style.textStyle.incl(styleDim) 6 | # of "styleUnknown", "styleItalic", "styleStandout": style.textStyle.incl(styleUnknown) 7 | # of "styleUnderscore", "styleUnderline": style.textStyle.incl(styleUnderscore) 8 | # of "styleBlink": style.textStyle.incl(styleBlink) 9 | # of "styleReverse", "styleInverse": style.textStyle.incl(styleReverse) 10 | # of "styleHidden": style.textStyle.incl(styleHidden) 11 | 12 | [window] 13 | bgColor16 = "bgBlue" 14 | bgColor256 = "MidnightBlue" 15 | bgColorRGB = "MidnightBlue" 16 | fgColor16 = "fgWhite" 17 | fgColor256 = "White" 18 | fgColorRGB = "White" 19 | border = "none" 20 | 21 | [dock] 22 | bgColor16 = "bgBlack" 23 | bgColor256 = "Grey3" 24 | bgColorRGB = "48,48,42" 25 | fgColor16 = "fgWhite" 26 | fgColor256 = "White" 27 | fgColorRGB = "White" 28 | border = "none" 29 | 30 | 31 | [input] 32 | bgColor16 = 47 33 | bgColor256 = 254 34 | bgColorRGB = "255,255,255" 35 | fgColor16 = 30 36 | fgColor256 = 0 37 | fgColorRGB = "black" 38 | #"0,0,135" 39 | #textStyle = "styleBright" 40 | border = "none" 41 | 42 | [input-focus] 43 | bgColor16 = 43 44 | bgColor256 = 222 45 | bgColorRGB = "255,215,95" 46 | fgColor16 = 30 47 | fgColor256 = 0 48 | fgColorRGB = "0,0,135" 49 | textStyle = "styleUnknown" 50 | border = "none" 51 | 52 | [input-drag] 53 | bgColor16 = 44 54 | bgColor256 = 128 55 | bgColorRGB = "175,0,215" 56 | fgColor16 = 30 57 | fgColor256 = 0 58 | fgColorRGB = "0,0,135" 59 | textStyle = "styleBlink" 60 | border = "none" 61 | 62 | [input-disabled] 63 | bgColor16 = 40 64 | bgColor256 = 245 65 | bgColorRGB = "100,100,100" 66 | fgColor16 = 37 67 | fgColor256 = 90 68 | fgColorRGB = "222,222,222" 69 | border = "none" 70 | 71 | 72 | 73 | [input-even] 74 | bgColor16 = 40 75 | bgColor256 = 237 76 | bgColorRGB = "gainsboro" 77 | fgColor16 = 30 78 | fgColor256 = 0 79 | fgColorRGB = "black" 80 | border = "none" 81 | 82 | [input-odd] 83 | bgColor16 = 40 84 | bgColor256 = 235 85 | bgColorRGB = "ghostwhite" 86 | fgColor16 = 30 87 | fgColor256 = 0 88 | fgColorRGB = "black" 89 | border = "none" 90 | 91 | [input-even_dark] 92 | bgColor16 = 37 93 | bgColor256 = 237 94 | bgColorRGB = "0,0,0" 95 | fgColor16 = 30 96 | fgColor256 = "white" 97 | fgColorRGB = "white" 98 | border = "none" 99 | 100 | [input-odd_dark] 101 | bgColor16 = 37 102 | bgColor256 = 235 103 | bgColorRGB = "25,25,25" 104 | fgColor16 = 30 105 | fgColor256 = "white" 106 | fgColorRGB = "white" 107 | border = "none" -------------------------------------------------------------------------------- /tests/template_simpleapp/stui/theme.tss: -------------------------------------------------------------------------------- 1 | # proc setTextStyle*(style: StyleSheetRef, textStyleStr: string)= 2 | # case textStyleStr: 3 | # of "": discard 4 | # of "styleBright", "styleBold": style.textStyle.incl(styleBright) 5 | # of "styleDim": style.textStyle.incl(styleDim) 6 | # of "styleUnknown", "styleItalic", "styleStandout": style.textStyle.incl(styleUnknown) 7 | # of "styleUnderscore", "styleUnderline": style.textStyle.incl(styleUnderscore) 8 | # of "styleBlink": style.textStyle.incl(styleBlink) 9 | # of "styleReverse", "styleInverse": style.textStyle.incl(styleReverse) 10 | # of "styleHidden": style.textStyle.incl(styleHidden) 11 | 12 | [window] 13 | bgColor16 = "bgBlue" 14 | bgColor256 = "MidnightBlue" 15 | bgColorRGB = "MidnightBlue" 16 | fgColor16 = "fgWhite" 17 | fgColor256 = "White" 18 | fgColorRGB = "White" 19 | border = "none" 20 | 21 | [dock] 22 | bgColor16 = "bgBlack" 23 | bgColor256 = "Grey3" 24 | bgColorRGB = "48,48,42" 25 | fgColor16 = "fgWhite" 26 | fgColor256 = "White" 27 | fgColorRGB = "White" 28 | border = "none" 29 | 30 | 31 | [input] 32 | bgColor16 = 47 33 | bgColor256 = 254 34 | bgColorRGB = "255,255,255" 35 | fgColor16 = 30 36 | fgColor256 = 0 37 | fgColorRGB = "black" 38 | #"0,0,135" 39 | #textStyle = "styleBright" 40 | border = "none" 41 | 42 | [input-focus] 43 | bgColor16 = 43 44 | bgColor256 = 222 45 | bgColorRGB = "255,215,95" 46 | fgColor16 = 30 47 | fgColor256 = 0 48 | fgColorRGB = "0,0,135" 49 | textStyle = "styleUnknown" 50 | border = "none" 51 | 52 | [input-drag] 53 | bgColor16 = 44 54 | bgColor256 = 128 55 | bgColorRGB = "175,0,215" 56 | fgColor16 = 30 57 | fgColor256 = 0 58 | fgColorRGB = "0,0,135" 59 | textStyle = "styleBlink" 60 | border = "none" 61 | 62 | [input-disabled] 63 | bgColor16 = 40 64 | bgColor256 = 245 65 | bgColorRGB = "100,100,100" 66 | fgColor16 = 37 67 | fgColor256 = 90 68 | fgColorRGB = "222,222,222" 69 | border = "none" 70 | 71 | 72 | 73 | [input-even] 74 | bgColor16 = 40 75 | bgColor256 = 237 76 | bgColorRGB = "gainsboro" 77 | fgColor16 = 30 78 | fgColor256 = 0 79 | fgColorRGB = "black" 80 | border = "none" 81 | 82 | [input-odd] 83 | bgColor16 = 40 84 | bgColor256 = 235 85 | bgColorRGB = "ghostwhite" 86 | fgColor16 = 30 87 | fgColor256 = 0 88 | fgColorRGB = "black" 89 | border = "none" 90 | 91 | [input-even_dark] 92 | bgColor16 = 37 93 | bgColor256 = 237 94 | bgColorRGB = "0,0,0" 95 | fgColor16 = 30 96 | fgColor256 = "white" 97 | fgColorRGB = "white" 98 | border = "none" 99 | 100 | [input-odd_dark] 101 | bgColor16 = 37 102 | bgColor256 = 235 103 | bgColorRGB = "25,25,25" 104 | fgColor16 = 30 105 | fgColor256 = "white" 106 | fgColorRGB = "white" 107 | border = "none" -------------------------------------------------------------------------------- /src/stui/terminal_extra.nim: -------------------------------------------------------------------------------- 1 | import os, osproc 2 | import termios 3 | 4 | #[ 5 | http://www.termsys.demon.co.uk/vtansi.htm 6 | ]# 7 | 8 | proc getColorMode*(): int = 9 | var str = getEnv("COLORTERM") #$execProcess("printenv COLORTERM") 10 | if str notin ["truecolor", "24bit"]: 11 | str = $execProcess("tput colors") 12 | 13 | case str: 14 | of "8\n": result = 0 15 | of "16\n": result = 1 16 | of "256\n": result = 2 17 | else: result = 1 18 | else: 19 | result = 3 20 | 21 | proc switchToAlternateBuffer*() {.inline.} = 22 | stdout.write "\e[?1049h\e[H" #and move to home position 23 | 24 | proc switchToNormalBuffer*() {.inline.} = 25 | stdout.write "\e[?1049l" 26 | 27 | 28 | proc setCursorPos*(x,y:int) {.inline.} = 29 | stdout.write("\e[" & $y & ";" & $x & "H") # H-ome : standard, more referenced 30 | #stdout.write("\e[" & $y & ";" & $x & "f") # f-orce : this should be it... 31 | 32 | 33 | #[ 34 | 35 | # not working as espected - disabling ECHO should remove mouse output on screen 36 | # and enable Channels/sleep on main thread but it hides cursor (?) 37 | 38 | proc disableEchoAndCanon*()= 39 | # from terminal readPassword 40 | let fd = stdin.getFileHandle() 41 | var cur: Termios 42 | discard fd.tcgetattr(cur.addr) 43 | cur.c_lflag = cur.c_lflag and not Cflag(ECHO) 44 | cur.c_lflag = cur.c_lflag or Cflag(ICANON) 45 | discard fd.tcsetattr(TCSANOW, cur.addr) #TCSADRAIN 46 | 47 | proc enableEchoAndCanon*()= 48 | # from terminal readPassword 49 | let fd = stdin.getFileHandle() 50 | var cur: Termios 51 | discard fd.tcgetattr(cur.addr) 52 | cur.c_lflag = cur.c_lflag or Cflag(ECHO) 53 | cur.c_lflag = cur.c_lflag and not Cflag(ICANON) 54 | discard fd.tcsetattr(TCSANOW, cur.addr) 55 | ]# 56 | 57 | # looks like resetting terminal resets this too, bt anyway... 58 | proc disableCanon*()= 59 | # from terminal readPassword 60 | let fd = stdin.getFileHandle() 61 | var cur: Termios 62 | discard fd.tcgetattr(cur.addr) 63 | cur.c_lflag = cur.c_lflag or Cflag(ICANON) 64 | discard fd.tcsetattr(TCSANOW, cur.addr) #TCSADRAIN 65 | 66 | # if not used, after stdout.write a '\n' needs to be written!! for screen update 67 | # looks like \n needs to be written anyway... ?! 68 | proc enableCanon*()= 69 | # from terminal readPassword 70 | let fd = stdin.getFileHandle() 71 | var cur: Termios 72 | discard fd.tcgetattr(cur.addr) 73 | cur.c_lflag = cur.c_lflag and not Cflag(ICANON) 74 | discard fd.tcsetattr(TCSANOW, cur.addr) 75 | 76 | 77 | proc setReversed*(){.inline.}= 78 | stdout.write "\e[7m" 79 | 80 | proc setDimmed*(){.inline.}= 81 | stdout.write "\e[2m" 82 | 83 | proc setUnderline*(){.inline.}= 84 | stdout.write "\e[4m" 85 | 86 | proc resetStyle*(){.inline.}= 87 | ## Reset all attributes 88 | stdout.write "\e[0m" 89 | 90 | proc resetTerminal*() {.inline.}= 91 | ## Reset all terminal settings to default 92 | stdout.write "\ec" 93 | 94 | 95 | proc clearScreen*(){.inline.}= 96 | ## Erases the screen with the background colour and moves the cursor to home. 97 | stdout.write "\e[2J\e[H" 98 | 99 | proc cursorForward*(n:int=1){.inline.}= 100 | ## Move Cursor to the right with n characters 101 | stdout.write "\e[" & $n & "C" 102 | -------------------------------------------------------------------------------- /src/stui/uiext_maximize.nim: -------------------------------------------------------------------------------- 1 | include "controll.inc.nim" 2 | 3 | 4 | type 5 | MaximizableControll* = ref object of Controll 6 | prev_win*: Window 7 | prev_x1*,prev_y1*,prev_x2*,prev_y2*, prev_width*,prev_heigth*:int # incl margins & borders! 8 | 9 | prev_width_unit*: string # used (by Tile) to store width unit: %, auto, ch(aracter) 10 | prev_width_value*:int # used (by Tile) for responsive width calc 11 | #heigth_unit*: string #? heigth unit is percent. 12 | prev_heigth_value*: int # stores % value in int 0-100 13 | prev_recalc*: proc(this_elem: Controll):void # if not nil called by Window.recalc() 14 | 15 | prev_visible*: bool # if `value=` fires draw(), decide if not to draw - Window.draw() 16 | 17 | isMaximized*: bool # if not maximized, draw label, else why do it? 18 | 19 | 20 | proc unMaximize*(this:MaximizableControll):void= 21 | this.x1 = this.prev_x1 22 | this.x2 = this.prev_x2 23 | this.y1 = this.prev_y1 24 | this.y2 = this.prev_y2 25 | this.width = this.prev_width 26 | this.heigth = this.prev_heigth 27 | this.width_unit = this.prev_width_unit 28 | this.width_value = this.prev_width_value 29 | this.heigth_value = this.prev_heigth_value 30 | this.recalc = this.prev_recalc 31 | this.visible = this.prev_visible 32 | this.width_value = this.prev_width_value 33 | this.heigth_value = this.prev_heigth_value 34 | this.win = this.prev_win 35 | 36 | this.isMaximized = false 37 | 38 | this.app.activeTile.windows.del(this.app.activeTile.windows.high) 39 | #this.app.activeWindow.draw() 40 | this.app.redraw() 41 | 42 | proc maximize*(this: MaximizableControll)= 43 | ## maximize a maximization-enabled Controll 44 | ## code xtracted from ui_fileselect 45 | 46 | ## create new window in current tile: 47 | #var parentWin = this.app.activeWindow 48 | this.prev_win = this.win 49 | this.win = this.app.activeTile.newWindow() 50 | #var page = this.win.newPage() 51 | 52 | this.win.x1 = this.prev_win.x1 53 | this.win.y1 = this.prev_win.y1 + 1 # +1 cascade 54 | this.win.x2 = this.prev_win.x2 55 | this.win.y2 = this.prev_win.y2 56 | this.win.width = this.prev_win.width 57 | this.win.heigth = this.win.y2 - this.win.y1 58 | 59 | var styleNormal: StyleSheetRef = new StyleSheetRef 60 | styleNormal.deepcopy this.win.app.styles["dock"] 61 | this.win.styles["window"] = styleNormal 62 | this.win.activeStyle = this.win.styles["window"] 63 | 64 | this.win.label = this.label 65 | this.isMaximized = true 66 | 67 | #................................................. 68 | 69 | #prepare 70 | this.prev_x1 = this.x1 71 | this.prev_x2 = this.x2 72 | this.prev_y1 = this.y1 73 | this.prev_y2 = this.y2 74 | this.prev_width = this.width 75 | this.prev_heigth = this.heigth 76 | this.prev_width_unit = this.width_unit 77 | this.prev_width_value = this.width_value 78 | this.prev_heigth_value = this.heigth_value 79 | this.prev_recalc = this.recalc 80 | this.recalc = nil 81 | this.prev_visible = this.visible 82 | #this.visible = true 83 | 84 | this.width_value = 100 85 | this.heigth_value = 100 86 | 87 | 88 | ## add controlls 89 | this.win.controlls.add(this) 90 | 91 | 92 | this.win.addEventListener("menu", proc(c:Controll) = unMaximize(this)) 93 | 94 | 95 | this.app.redraw() 96 | #this.visible = true 97 | #this.drawit(this,false) 98 | #this.app.activate(this) 99 | -------------------------------------------------------------------------------- /src/stui/appbase/appbase.nim: -------------------------------------------------------------------------------- 1 | import os, locks, times, tables 2 | import appbase/appbasetypes 3 | 4 | #[ type 5 | EventRoot* = object of RootObj#dummy base 6 | 7 | TimedAction* = tuple[name:string,interval:float,action:proc():void,lastrun:float] # epochTime:float 8 | 9 | # AppBase:------------------------------ 10 | # extend with your needed fields: MyApp* = ref object of AppBase 11 | AppBase* = ref object of RootObj 12 | listeners*: seq[tuple[name:string, actions: seq[proc():void]]] 13 | timers*: seq[TimedAction] #seq[tuple[name:string,interval:float,action:proc()]] 14 | 15 | # Main Channel [ msgtyp: int, msg: json ] 16 | ChannelJson* = Channel[tuple[msgtyp:int, msg:string]] 17 | MainChannelJsonFeeds* = Table[int, ChannelJson] # TH id, Json 18 | InterCom* = Table[string, MainChannelJsonFeeds] ]# 19 | 20 | #------------------------------------------------------------------------------- 21 | #[ # Intercomm functions; ChannelJson; Main Channel [ msgtyp: int, msg: json ] 22 | proc subscribe*(feed: var MainChannelJsonFeeds, thId: int, chan:ChannelJson)= 23 | feed[thId]= chan 24 | 25 | proc broadcast*(feed: var MainChannelJsonFeeds, msgtyp:int, msg:string)= 26 | for thId, listener in feed: 27 | feed[thId].send((msgtyp:msgtyp, msg:msg)) ]# 28 | 29 | #------------------------------------------------------------------------------- 30 | # todo: make a paralell version: 31 | proc runTimers*(this:AppBase)= 32 | var time = epochTime() 33 | if this.timers.len > 0: 34 | for iT in 0..this.timers.high: 35 | if time - this.timers[iT].lastrun >= this.timers[iT].interval: 36 | this.timers[iT].action() 37 | this.timers[iT].lastrun = time 38 | sleep(0) 39 | 40 | #------------------------------------------------------------------------------- 41 | 42 | 43 | proc addEventListener*(app:AppBase, evtname:string, fun:proc():void)= 44 | var exists = false 45 | var newListener: tuple[name:string, actions: seq[proc():void]] 46 | for i in 0..app.listeners.high: 47 | if app.listeners[i].name == evtname: 48 | app.listeners[i].actions.add(fun) 49 | exists = true 50 | if not exists: 51 | newListener.name = evtname 52 | newListener.actions = @[] 53 | newListener.actions.add(fun) 54 | app.listeners.add(newListener) 55 | 56 | 57 | proc removeEventListener*(app:AppBase, evtname:string, fun:proc():void)= 58 | for i in 0..app.listeners.high: 59 | if app.listeners[i].name == evtname: 60 | for j in 0..app.listeners[i].actions.high: 61 | if app.listeners[i].actions[j] == fun: 62 | app.listeners[i].actions.del(j) 63 | 64 | proc trigger*(app:AppBase, evtname:string )= 65 | for i in 0..app.listeners.high: 66 | if app.listeners[i].name == evtname: 67 | for j in 0..app.listeners[i].actions.high: 68 | app.listeners[i].actions[j]() 69 | 70 | 71 | template mainLoop*[T](app:T, eventhandler: untyped)= 72 | while true: 73 | 74 | when defined timedActions_enabled: app.runTimers() 75 | 76 | 77 | when defined mainChannelLog_enabled: handleLogChannel() 78 | when defined mainChannelInt_enabled: handleMainChannelInt() 79 | 80 | when defined mainChannelIntTalkback_enabled: handleMainChannelIntTalkback() 81 | 82 | when defined mainChannelIntChecked_enabled: handleMainChannelIntChecked() 83 | when defined mainChannelString_enabled: handleMainChannelString() 84 | when defined mainChannelJsonChecked_enabled: handleMainChannelJsonChecked() 85 | 86 | #...... 87 | when defined inputEventLoop_enabled: 88 | if inputLoopEventFlowVar.isReady():#! YOU define it in the body of template: 89 | 90 | eventhandler #! YOUR event handler that handles inputLoopEventFlowVar 91 | 92 | 93 | sleep(0) #! cpuRelax 94 | 95 | -------------------------------------------------------------------------------- /devtools/colorsRGB_enum: -------------------------------------------------------------------------------- 1 | #[ 2 | type ColorsRGB* = enum 3 | AliceBlue = 0xF0F8FF 4 | AntiqueWhite = 0xFAEBD7 5 | Aqua = 0x00FFFF 6 | Aquamarine = 0x7FFFD4 7 | Azure = 0xF0FFFF 8 | Beige = 0xF5F5DC 9 | Bisque = 0xFFE4C4 10 | Black = 0x000000 11 | BlanchedAlmond = 0xFFEBCD 12 | Blue = 0x0000FF 13 | BlueViolet = 0x8A2BE2 14 | Brown = 0xA52A2A 15 | BurlyWood = 0xDEB887 16 | CadetBlue = 0x5F9EA0 17 | Chartreuse = 0x7FFF00 18 | Chocolate = 0xD2691E 19 | Coral = 0xFF7F50 20 | CornflowerBlue = 0x6495ED 21 | Cornsilk = 0xFFF8DC 22 | Crimson = 0xDC143C 23 | Cyan = 0x00FFFF 24 | DarkBlue = 0x00008B 25 | DarkCyan = 0x008B8B 26 | DarkGoldenRod = 0xB8860B 27 | DarkGray = 0xA9A9A9 28 | DarkGreen = 0x006400 29 | DarkKhaki = 0xBDB76B 30 | DarkMagenta = 0x8B008B 31 | DarkOliveGreen = 0x556B2F 32 | Darkorange = 0xFF8C00 33 | DarkOrchid = 0x9932CC 34 | DarkRed = 0x8B0000 35 | DarkSalmon = 0xE9967A 36 | DarkSeaGreen = 0x8FBC8F 37 | DarkSlateBlue = 0x483D8B 38 | DarkSlateGray = 0x2F4F4F 39 | DarkTurquoise = 0x00CED1 40 | DarkViolet = 0x9400D3 41 | DeepPink = 0xFF1493 42 | DeepSkyBlue = 0x00BFFF 43 | DimGray = 0x696969 44 | DodgerBlue = 0x1E90FF 45 | FireBrick = 0xB22222 46 | FloralWhite = 0xFFFAF0 47 | ForestGreen = 0x228B22 48 | Fuchsia = 0xFF00FF 49 | Gainsboro = 0xDCDCDC 50 | GhostWhite = 0xF8F8FF 51 | Gold = 0xFFD700 52 | GoldenRod = 0xDAA520 53 | Gray = 0x808080 54 | Green = 0x008000 55 | GreenYellow = 0xADFF2F 56 | HoneyDew = 0xF0FFF0 57 | HotPink = 0xFF69B4 58 | IndianRed = 0xCD5C5C 59 | Indigo = 0x4B0082 60 | Ivory = 0xFFFFF0 61 | Khaki = 0xF0E68C 62 | Lavender = 0xE6E6FA 63 | LavenderBlush = 0xFFF0F5 64 | LawnGreen = 0x7CFC00 65 | LemonChiffon = 0xFFFACD 66 | LightBlue = 0xADD8E6 67 | LightCoral = 0xF08080 68 | LightCyan = 0xE0FFFF 69 | LightGoldenRodYellow = 0xFAFAD2 70 | LightGrey = 0xD3D3D3 71 | LightGreen = 0x90EE90 72 | LightPink = 0xFFB6C1 73 | LightSalmon = 0xFFA07A 74 | LightSeaGreen = 0x20B2AA 75 | LightSkyBlue = 0x87CEFA 76 | LightSlateGray = 0x778899 77 | LightSteelBlue = 0xB0C4DE 78 | LightYellow = 0xFFFFE0 79 | Lime = 0x00FF00 80 | LimeGreen = 0x32CD32 81 | Linen = 0xFAF0E6 82 | Magenta = 0xFF00FF 83 | Maroon = 0x800000 84 | MediumAquaMarine = 0x66CDAA 85 | MediumBlue = 0x0000CD 86 | MediumOrchid = 0xBA55D3 87 | MediumPurple = 0x9370D8 88 | MediumSeaGreen = 0x3CB371 89 | MediumSlateBlue = 0x7B68EE 90 | MediumSpringGreen = 0x00FA9A 91 | MediumTurquoise = 0x48D1CC 92 | MediumVioletRed = 0xC71585 93 | MidnightBlue = 0x191970 94 | MintCream = 0xF5FFFA 95 | MistyRose = 0xFFE4E1 96 | Moccasin = 0xFFE4B5 97 | NavajoWhite = 0xFFDEAD 98 | Navy = 0x000080 99 | OldLace = 0xFDF5E6 100 | Olive = 0x808000 101 | OliveDrab = 0x6B8E23 102 | Orange = 0xFFA500 103 | OrangeRed = 0xFF4500 104 | Orchid = 0xDA70D6 105 | PaleGoldenRod = 0xEEE8AA 106 | PaleGreen = 0x98FB98 107 | PaleTurquoise = 0xAFEEEE 108 | PaleVioletRed = 0xD87093 109 | PapayaWhip = 0xFFEFD5 110 | PeachPuff = 0xFFDAB9 111 | Peru = 0xCD853F 112 | Pink = 0xFFC0CB 113 | Plum = 0xDDA0DD 114 | PowderBlue = 0xB0E0E6 115 | Purple = 0x800080 116 | Red = 0xFF0000 117 | RosyBrown = 0xBC8F8F 118 | RoyalBlue = 0x4169E1 119 | SaddleBrown = 0x8B4513 120 | Salmon = 0xFA8072 121 | SandyBrown = 0xF4A460 122 | SeaGreen = 0x2E8B57 123 | SeaShell = 0xFFF5EE 124 | Sienna = 0xA0522D 125 | Silver = 0xC0C0C0 126 | SkyBlue = 0x87CEEB 127 | SlateBlue = 0x6A5ACD 128 | SlateGray = 0x708090 129 | Snow = 0xFFFAFA 130 | SpringGreen = 0x00FF7F 131 | SteelBlue = 0x4682B4 132 | Tan = 0xD2B48C 133 | Teal = 0x008080 134 | Thistle = 0xD8BFD8 135 | Tomato = 0xFF6347 136 | Turquoise = 0x40E0D0 137 | Violet = 0xEE82EE 138 | Wheat = 0xF5DEB3 139 | White = 0xFFFFFF 140 | WhiteSmoke = 0xF5F5F5 141 | Yellow = 0xFFFF00 142 | YellowGreen = 0x9ACD32 143 | ]# -------------------------------------------------------------------------------- /tests/template_simpleapp/template_simpleapp.nim: -------------------------------------------------------------------------------- 1 | import threadpool, locks, os, tables 2 | import stui, stui/kmloop 3 | import terminal, stui/terminal_extra, colors, stui/colors_extra 4 | 5 | import stui/ui_menu 6 | 7 | 8 | proc newSimpleApp(): App = 9 | # Basic App init 10 | result = newApp(splitPath(currentSourcePath()).head ) # (path to themes) 11 | # at start, set activeWorkspace & activeTile manually! 12 | result.activeWorkSpace = result.newWorkSpace("WorkSpace1") 13 | result.activeTile = result.activeWorkSpace.newTile("auto") 14 | discard result.activeTile.newWindow("Window1") 15 | result.activeWindow.styles["window"].padding.left = 1 16 | result.activeWindow.styles["window"].padding.top = 1 17 | 18 | var app = newSimpleApp() 19 | 20 | # MENU ------------------------------------------------------------------------- 21 | var menu1 = app.newMenu() 22 | menu1.setMargin("all",2) 23 | discard menu1.currentNode.addChild("Quit",proc() = quit()) 24 | app.activeWindow.addEventListener("menu", proc(c:Controll) = menu1.show()) 25 | 26 | 27 | ################################################################################ 28 | ################################################################################ 29 | # INCLUDE YOUR LINES HERE: ----------------------------------------------------- 30 | 31 | import stui/ui_splash 32 | 33 | var splash = app.activeWindow.newSplash(""" 34 | ███████╗████████╗██╗ ██╗██╗ 35 | ██╔════╝╚══██╔══╝██║ ██║██║ 36 | ███████╗ ██║ ██║ ██║██║ 37 | ╚════██║ ██║ ██║ ██║██║ 38 | ███████║ ██║ ╚██████╔╝██║ 39 | ╚══════╝ ╚═╝ ╚═════╝ ╚═╝""") 40 | 41 | 42 | 43 | 44 | ################################################################################ 45 | ################################################################################ 46 | 47 | 48 | 49 | #! RUN ☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰ 50 | #! terminal init and app starts to run here 51 | #! create gui controlls BEFORE this 52 | 53 | app.initTerminal() 54 | 55 | splash.draw() 56 | sleep(1000) 57 | 58 | app.recalc() 59 | app.draw() 60 | 61 | 62 | #! MAIN LOOP-------------------------------------------------------------------- 63 | 64 | var inputLoopEvent: KMEvent #! (i) from myappbasetypes 65 | var inputLoopEventFlowVar: FlowVar[KMEvent] 66 | 67 | #! MAIN LOOP STARTS HERE: 68 | inputLoopEventFlowVar = spawn kmLoop() #! REPLACE PROC WITH YOURS #1/2 <<<<<<< 69 | 70 | while true: 71 | inputLoopEvent = KMEvent(^inputLoopEventFlowVar) # it stops here anyway... 72 | 73 | #! PROCESS MESSAGE HERE -- case inputLoopEvent, of x: etc 74 | 75 | case inputLoopEvent.evType: 76 | of "Click","Release","Drag","Drop", "ScrollUp","ScrollDown", "DoubleClick": 77 | app.mouseEventHandler(inputLoopEvent) 78 | 79 | of "FnKey","CtrlKey", "Char": # vscode terminal middle mouse click triggers this too... 80 | # KeyPgUp, KeyPgDown trigger controlls cb first then apps 81 | if inputLoopEvent.key in [KeyPgUp, KeyPgDown]: 82 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 83 | app.activeControll.onKeypress(app.activeControll, inputLoopEvent) 84 | elif app.onKeypress != nil: 85 | discard app.onKeypress(app, inputLoopEvent) 86 | else: # trigger apps cb first, then controlls 87 | if app.onKeypress != nil and app.onKeypress(app, inputLoopEvent) == false: 88 | # if app not handles this, then try controlls 89 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 90 | app.activeControll.onKeypress(app.activeControll, inputLoopEvent) 91 | 92 | of "EXIT": 93 | if app.activeControll != nil: 94 | try: 95 | if app.activeControll.cancel != nil: 96 | app.activeControll.cancel(app.activeControll) 97 | app.activeControll = nil 98 | except: 99 | break # == quit 100 | else: 101 | break # == quit 102 | else: discard 103 | 104 | ##! RESTART LOOP: 105 | inputLoopEventFlowVar = spawn kmLoop() #! REPLACE PROC WITH YOURS #2/2 <<<<<<< 106 | 107 | 108 | ################################################################################ 109 | ################################################################################ 110 | -------------------------------------------------------------------------------- /tests/template/channeltestmodule.nim: -------------------------------------------------------------------------------- 1 | import appbase, appbase/myappbasetypes, threadpool, tables, os, json, 2 | appbase/intchannelutils 3 | 4 | var 5 | app*: MyApp 6 | interCom*: InterCom 7 | mainChannelIntTalkback*: McitChannel 8 | mainChannelString*: McsChannel 9 | mainChannelLog*: LogChannel 10 | 11 | #!------------- 12 | let 13 | mThreadId* = 3 14 | 15 | 16 | var replyJChannel = new ChannelJson 17 | 18 | proc testJ1*()= 19 | 20 | #open replyJChannel[] 21 | 22 | #open mainChannelLog[] 23 | when defined logger_enabled: mainChannelLog[].send("N [testJ1] addr: " & $ cast[uint](addr(replyJChannel)) ) 24 | 25 | var testJson = newJObject() 26 | testJson.add("typ", newJInt(101)) # 101: current code for add channel to intercom - see mainChannelJson.inc.nim 27 | testJson.add("from", newJInt(mThreadId)) # sender. and this id will be registered i intercom 28 | testJson.add("feed", newJInt(0)) # the feed/branch to register into 29 | testJson.add("chanPtr", %(addr replyJChannel)) 30 | #when defined logger_enabled: mainChannelLog[].send("N" & $ mThreadId & $(type interCom[0]) & $ app.threadId) 31 | interCom[0].sendTo(0, $testJson) 32 | when defined logger_enabled: mainChannelLog[].send("N [testJ1]") 33 | 34 | #!------------------------------------------------------------------------------ 35 | 36 | proc testJ2*(interCom: ptr InterCom){.thread.}= 37 | let 38 | mThreadId = 31 39 | 40 | var replyJChannel31 = new ChannelJson 41 | var testJson = newJObject() 42 | testJson.add("typ", newJInt(101)) 43 | testJson.add("from", newJInt(mThreadId)) 44 | testJson.add("feed", newJInt(0)) 45 | testJson.add("chanPtr", %(addr replyJChannel31)) 46 | 47 | #when defined logger_enabled: mainChannelLog[].send("N" & $ mThreadId & $ testJson) 48 | 49 | var icom = interCom[] 50 | #when defined logger_enabled: mainChannelLog[].send("N [testJ2] subscribe result > " & $ 51 | discard icom.subscribe(mThreadId, replyJChannel31) # TODO LOG 52 | 53 | #........... 54 | 55 | var 56 | th31: Thread[ptr InterCom] 57 | proc testJ3*()= 58 | 59 | th31.createThread(testJ2, addr interCom) 60 | 61 | #!------------------------------------------------------------------------------ 62 | 63 | proc testIntTalkBack*(args:tuple[mcit: ptr McitChannel, mclog: ptr LogChannel]){.thread.}= 64 | #[ let 65 | mThreadId = 41 ]# 66 | 67 | var 68 | msg: int 69 | resp: int 70 | chan: McitChannel 71 | logchan: LogChannel 72 | (mainChannelIntTalkback, mclog) = args 73 | 74 | logchan = mclog[] 75 | #open logchan[] 76 | 77 | chan = mainChannelIntTalkback[] 78 | msg = packIntMsg([128.uint,242.uint,9.uint,64.uint]).int 79 | #open chan[] 80 | chan[].send(addr msg) 81 | resp = msg 82 | 83 | when defined logger_enabled: logchan[].send("N {{testIntTalkBack}} Sent..." & $ msg) 84 | when defined logger_enabled: logchan[].send( "D {{testIntTalkBack}} peek: " & $ chan[].peek()) 85 | 86 | while resp == msg: 87 | sleep(1) 88 | when defined logger_enabled: logchan[].send( "N {{testIntTalkBack}} Received mainChannelIntTalkback") 89 | 90 | msg = packIntMsg([255.uint,44.uint,0.uint,128.uint]).int 91 | chan.waitFor(msg) 92 | when defined logger_enabled: logchan[].send("D {{testIntTalkBack}} Received waitFor mainChannelIntTalkback") 93 | 94 | 95 | #................................... 96 | 97 | var 98 | th41: Thread[tuple[mcit: ptr McitChannel, mclog: ptr LogChannel]] 99 | 100 | proc testMainChannelIntTalkback*()= 101 | 102 | th41.createThread(testIntTalkBack, (mcit: addr mainChannelIntTalkback, mclog: addr mainChannelLog) ) 103 | 104 | #!------------------------------------------------------------------------------ 105 | 106 | 107 | proc testMcsChannel*(args:tuple[mcs: ptr McsChannel, mclog: ptr LogChannel]){.thread.}= 108 | #[ let 109 | mThreadId = 51 ]# 110 | 111 | var 112 | msg: string = "hello" 113 | (mainChannelString, logchan) = args 114 | 115 | #open logchan[][] 116 | 117 | #open mainChannelString[][] 118 | mainChannelString[][].send(msg) 119 | when defined logger_enabled: logchan[][].send( "N $$$ testMcsChannel $$$ Sent..." & msg) 120 | #when defined logger_enabled: notice( " $$$ testMcsChannel $$$ peek: ",mainChannelString[][].peek()) 121 | 122 | #[ sleep(1500) 123 | msg = "quit" 124 | mainChannelString[][].send(msg) 125 | when defined logger_enabled: logchan[][].send( "N $$$ testMcsChannel $$$ Sent..." & msg) ]# 126 | 127 | var 128 | thStr51: Thread[tuple[mcs: ptr McsChannel, mclog: ptr LogChannel]] 129 | 130 | proc testMainChannelString*()= 131 | thStr51.createThread(testMcsChannel,(mcs: addr mainChannelString, mclog: addr mainChannelLog)) -------------------------------------------------------------------------------- /src/stui/appbase/mainloop.inc.nim: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | ################################################################################ 4 | ################################################################################ 5 | ################################################################################ 6 | # 7 | # ███▄ ▄███▓ ▄▄▄ ██▓ ███▄ █ ██▓ ▒█████ ▒█████ ██▓███ 8 | # ▓██▒▀█▀ ██▒▒████▄ ▓██▒ ██ ▀█ █ ▓██▒ ▒██▒ ██▒▒██▒ ██▒▓██░ ██▒ 9 | # ▓██ ▓██░▒██ ▀█▄ ▒██▒▓██ ▀█ ██▒ ▒██░ ▒██░ ██▒▒██░ ██▒▓██░ ██▓▒ 10 | # ▒██ ▒██ ░██▄▄▄▄██ ░██░▓██▒ ▐▌██▒ ▒██░ ▒██ ██░▒██ ██░▒██▄█▓▒ ▒ 11 | # ▒██▒ ░██▒ ▓█ ▓██▒░██░▒██░ ▓██░ ░██████▒░ ████▓▒░░ ████▓▒░▒██▒ ░ ░ 12 | # ░ ▒░ ░ ░ ▒▒ ▓▒█░░▓ ░ ▒░ ▒ ▒ ░ ▒░▓ ░░ ▒░▒░▒░ ░ ▒░▒░▒░ ▒▓▒░ ░ ░ 13 | # ░ ░ ░ ▒ ▒▒ ░ ▒ ░░ ░░ ░ ▒░ ░ ░ ▒ ░ ░ ▒ ▒░ ░ ▒ ▒░ ░▒ ░ 14 | # ░ ░ ░ ▒ ▒ ░ ░ ░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒ ░░ 15 | # ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ 16 | ################################################################################ 17 | ################################################################################ 18 | ################################################################################ 19 | ################################################################################ 20 | 21 | import os, locks, times, threadpool 22 | 23 | var app: AppBase 24 | 25 | #! ADD APP INIT HERE 26 | app = new AppBase 27 | 28 | #------------------------------------------------------------------------------- 29 | #import times 30 | 31 | proc sampleTimedAction()= 32 | sleep(200) 33 | 34 | 35 | #............. 36 | 37 | var 38 | 39 | sTA:TimedAction=( 40 | name:"sampleTimedAction", 41 | interval: 2.float, 42 | action: nil, #? O.o: adding checkTerminalResized here results error 43 | lastrun:epochTime() 44 | ) 45 | 46 | sTA.action = sampleTimedAction 47 | 48 | app.timers.add(sTA) 49 | 50 | 51 | 52 | #------------------------------------------------------------------------------- 53 | # SIMPLE InterCom 54 | var mainChannelString : Channel[string] 55 | mainChannelString.open() 56 | 57 | 58 | proc handleMainChannelString()= 59 | for iMsg in 0..2: #? anti flood 60 | var inbox = tryRecv( (mainChannelString) ) #tuple[dataAvailable: bool, msg: TMsg] 61 | if inbox.dataAvailable: 62 | case inbox.msg: 63 | of "redraw": 64 | discard #app.redraw() 65 | of "quit": 66 | quit() 67 | else: 68 | app.trigger(inbox.msg) 69 | else: break 70 | #------------------------------------------------------------------------------- 71 | # SIMPLE INT InterCom -- maybe int is the fastest? 72 | # enum to int for readability? 73 | 74 | var mainChannelInt : Channel[int] 75 | mainChannelInt.open() 76 | 77 | 78 | proc handleMainChannelInt()= 79 | for iMsg in 0..2: #? anti flood 80 | var inbox = tryRecv( (mainChannelInt) ) #tuple[dataAvailable: bool, msg: TMsg] 81 | if inbox.dataAvailable: 82 | case inbox.msg: 83 | of 1: #"redraw": 84 | discard #app.redraw() 85 | of int.high: #"quit": 86 | quit() 87 | else: 88 | app.trigger($ inbox.msg) 89 | else: break 90 | #------------------------------------------------------------------------------- 91 | 92 | 93 | 94 | #! var kmloopFlowVar = spawn kmLoop() #KMEvent 95 | #! var kmEvent: EventRoot 96 | block LOOP: 97 | while true: 98 | 99 | app.runTimers() 100 | #...... 101 | when defined mainChannelInt_enabled: handleMainChannelInt() 102 | when defined mainChannelString_enabled: handleMainChannelString() 103 | 104 | #...... 105 | 106 | if inputLoopEventFlowVar.isReady(): #! it consumes LOT of cpu | req by: app.runTimers() 107 | 108 | inputLoopEvent = EventRoot(^inputLoopEventFlowVar) # it stops here anyway... 109 | 110 | # PROCESS MESSAGE HERE 111 | 112 | inputLoopEventFlowVar = spawn inputEventLoop() #EventRoot 113 | 114 | sleep(0) #! cpuRelax 115 | 116 | 117 | #app.close() 118 | #............ 119 | ################################################################################ 120 | ################################################################################ 121 | ################################################################################ 122 | ################################################################################ -------------------------------------------------------------------------------- /tests/splashtests/string/stringsplash.nim: -------------------------------------------------------------------------------- 1 | import threadpool, locks, os, tables 2 | import stui, stui/kmloop 3 | import terminal, stui/terminal_extra, colors, stui/colors_extra 4 | 5 | import stui/ui_menu 6 | 7 | 8 | proc newSimpleApp(): App = 9 | # Basic App init 10 | result = newApp(splitPath(currentSourcePath()).head ) # (path to themes) 11 | # at start, set activeWorkspace & activeTile manually! 12 | result.activeWorkSpace = result.newWorkSpace("WorkSpace1") 13 | result.activeTile = result.activeWorkSpace.newTile("auto") 14 | discard result.activeTile.newWindow("Window1") 15 | result.activeWindow.styles["window"].padding.left = 1 16 | result.activeWindow.styles["window"].padding.top = 1 17 | 18 | var app = newSimpleApp() 19 | 20 | # MENU ------------------------------------------------------------------------- 21 | var menu1 = app.newMenu() 22 | menu1.setMargin("all",2) 23 | discard menu1.currentNode.addChild("Quit",proc() = quit()) 24 | app.activeWindow.addEventListener("menu", proc(c:Controll) = menu1.show()) 25 | 26 | 27 | ################################################################################ 28 | ################################################################################ 29 | # INCLUDE YOUR LINES HERE: ----------------------------------------------------- 30 | 31 | 32 | 33 | import stui/ui_splash 34 | 35 | var splash = app.activeWindow.newSplash(""" 36 | ███████╗████████╗██╗ ██╗██╗ 37 | ██╔════╝╚══██╔══╝██║ ██║██║ 38 | ███████╗ ██║ ██║ ██║██║ 39 | ╚════██║ ██║ ██║ ██║██║ 40 | ███████║ ██║ ╚██████╔╝██║ 41 | ╚══════╝ ╚═╝ ╚═════╝ ╚═╝""") 42 | 43 | #............................................................................... 44 | 45 | 46 | import stui/ui_button 47 | 48 | var splashBtn = app.activeWindow.newButton("SPLASH",2,2) 49 | splashBtn.setMargin("top", 1) 50 | 51 | proc splashBtnClick(this:Controll)= 52 | splash.drawit(splash, true) 53 | sleep(1000) 54 | splash.app.recalc() 55 | splash.app.draw() 56 | 57 | splashBtn.addEventListener("click", splashBtnClick) 58 | 59 | 60 | 61 | var quitBtn = app.activeWindow.newButton("quit",1,1) 62 | quitBtn.setMargin("top", 1) 63 | 64 | proc quitBtnClick(this:Controll)= 65 | this.app.quit(QuitSuccess) 66 | 67 | quitBtn.addEventListener("click", quitBtnClick) 68 | 69 | 70 | 71 | ################################################################################ 72 | ################################################################################ 73 | 74 | 75 | 76 | #! RUN ☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰ 77 | #! terminal init and app starts to run here 78 | #! create gui controlls BEFORE this 79 | 80 | app.initTerminal() 81 | 82 | splash.draw() 83 | sleep(1000) 84 | 85 | app.recalc() 86 | app.draw() 87 | 88 | 89 | #! MAIN LOOP-------------------------------------------------------------------- 90 | 91 | var inputLoopEvent: KMEvent #! (i) from myappbasetypes 92 | var inputLoopEventFlowVar: FlowVar[KMEvent] 93 | 94 | #! MAIN LOOP STARTS HERE: 95 | inputLoopEventFlowVar = spawn kmLoop() #! REPLACE PROC WITH YOURS #1/2 <<<<<<< 96 | 97 | while true: 98 | inputLoopEvent = KMEvent(^inputLoopEventFlowVar) # it stops here anyway... 99 | 100 | #! PROCESS MESSAGE HERE -- case inputLoopEvent, of x: etc 101 | 102 | case inputLoopEvent.evType: 103 | of "Click","Release","Drag","Drop", "ScrollUp","ScrollDown", "DoubleClick": 104 | app.mouseEventHandler(inputLoopEvent) 105 | 106 | of "FnKey","CtrlKey", "Char": # vscode terminal middle mouse click triggers this too... 107 | # KeyPgUp, KeyPgDown trigger controlls cb first then apps 108 | if inputLoopEvent.key in [KeyPgUp, KeyPgDown]: 109 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 110 | app.activeControll.onKeypress(app.activeControll, inputLoopEvent) 111 | elif app.onKeypress != nil: 112 | discard app.onKeypress(app, inputLoopEvent) 113 | else: # trigger apps cb first, then controlls 114 | if app.onKeypress != nil and app.onKeypress(app, inputLoopEvent) == false: 115 | # if app not handles this, then try controlls 116 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 117 | app.activeControll.onKeypress(app.activeControll, inputLoopEvent) 118 | 119 | of "EXIT": 120 | if app.activeControll != nil: 121 | try: 122 | if app.activeControll.cancel != nil: 123 | app.activeControll.cancel(app.activeControll) 124 | app.activeControll = nil 125 | except: 126 | break # == quit 127 | else: 128 | break # == quit 129 | else: discard 130 | 131 | ##! RESTART LOOP: 132 | inputLoopEventFlowVar = spawn kmLoop() #! REPLACE PROC WITH YOURS #2/2 <<<<<<< 133 | 134 | 135 | ################################################################################ 136 | ################################################################################ 137 | -------------------------------------------------------------------------------- /tests/test_old/mainloop.inc.nim: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | ################################################################################ 4 | ################################################################################ 5 | ################################################################################ 6 | # 7 | # ███╗ ███╗ █████╗ ██╗███╗ ██╗██╗ ██████╗ ██████╗ ██████╗ 8 | # ████╗ ████║██╔══██╗██║████╗ ██║██║ ██╔═══██╗██╔═══██╗██╔══██╗ 9 | # ██╔████╔██║███████║██║██╔██╗ ██║██║ ██║ ██║██║ ██║██████╔╝ 10 | # ██║╚██╔╝██║██╔══██║██║██║╚██╗██║██║ ██║ ██║██║ ██║██╔═══╝ 11 | # ██║ ╚═╝ ██║██║ ██║██║██║ ╚████║███████╗╚██████╔╝╚██████╔╝██║ 12 | # ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ 13 | # 14 | ################################################################################ 15 | ################################################################################ 16 | ################################################################################ 17 | ################################################################################ 18 | 19 | import times 20 | 21 | app.initTerminal() 22 | app.recalc() 23 | release(app.termlock) 24 | app.draw() 25 | #------------------------------------------------------------------------------- 26 | 27 | proc checkTerminalResized()= 28 | ## used as/via TimedAction action 29 | ## used internally 30 | if app.terminalHeight != terminalHeight() or app.terminalWidth != terminalWidth(): 31 | app.terminalHeight = terminalHeight() 32 | app.terminalWidth = terminalWidth() 33 | app.recalc() 34 | for iws in app.workSpaces: 35 | for it in iws.tiles: 36 | for iw in it.windows: 37 | if iw.currentPage > iw.pages.high: iw.currentPage = iw.pages.high 38 | app.draw() 39 | #............. 40 | 41 | var 42 | rT:TimedAction=( 43 | name:"termresize", 44 | interval: 2.float, 45 | action: nil, #O.o: adding checkTerminalResized here results error 46 | lastrun:epochTime() 47 | ) 48 | 49 | rT.action = checkTerminalResized 50 | app.timers.add(rT) 51 | #------------------------------------------------------------------------------- 52 | 53 | var mainChannel : Channel[string] 54 | mainChannel.open() 55 | 56 | app.itc = addr mainChannel 57 | 58 | proc runChannels()= 59 | for iMsg in 0..2: # anti flood 60 | var inbox = tryRecv( (mainChannel) ) #tuple[dataAvailable: bool, msg: TMsg] 61 | if inbox.dataAvailable: 62 | case inbox.msg: 63 | of "redraw": 64 | app.redraw() 65 | of "quit": 66 | quit() 67 | else: 68 | app.trigger(inbox.msg) 69 | else: break 70 | #------------------------------------------------------------------------------- 71 | 72 | 73 | 74 | #------------------------------------------------------------------------------- 75 | 76 | var kmloopFlowVar = spawn kmLoop() #KMEvent 77 | var kmEvent: KMEvent 78 | while true: 79 | 80 | app.runTimers() 81 | #...... 82 | 83 | runChannels() 84 | #...... 85 | 86 | if kmloopFlowVar.isReady(): #! it consumes LOT of cpu | req by: app.runTimers() 87 | 88 | kmEvent = KMEvent(^kmloopFlowVar) # it stops here anyway... 89 | 90 | case kmEvent.evType: 91 | of "Click","Release","Drag","Drop", "ScrollUp","ScrollDown", "DoubleClick": 92 | app.mouseEventHandler(kmEvent) 93 | 94 | 95 | of "FnKey","CtrlKey", "Char": # vscode terminal middle mouse click triggers this too... 96 | # KeyPgUp, KeyPgDown trigger controlls cb first then apps 97 | if kmEvent.key in [KeyPgUp, KeyPgDown]: 98 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 99 | app.activeControll.onKeypress(app.activeControll, kmEvent) 100 | else: 101 | if app.onKeypress != nil: 102 | discard app.onKeypress(app, kmEvent) 103 | else: # trigger apps cb first, then controlls 104 | if app.onKeypress != nil and app.onKeypress(app, kmEvent) == false:#! changed: 105 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 106 | app.activeControll.onKeypress(app.activeControll, kmEvent) 107 | 108 | of "EXIT": 109 | if app.activeControll != nil: 110 | try: 111 | if app.activeControll.cancel != nil: 112 | app.activeControll.cancel(app.activeControll) 113 | app.activeControll = nil 114 | except: 115 | break # == quit 116 | else: 117 | break # == quit 118 | else: discard 119 | 120 | kmloopFlowVar = spawn kmLoop() #KMEvent 121 | 122 | sleep(0) 123 | 124 | 125 | app.closeTerminal() 126 | 127 | ################################################################################ 128 | ################################################################################ 129 | ################################################################################ 130 | ################################################################################ -------------------------------------------------------------------------------- /src/stui/ui_splash.nim: -------------------------------------------------------------------------------- 1 | #import stui, terminal, colors, colors_extra, colors256, unicode, tables, os, locks 2 | include "controll.inc.nim" 3 | 4 | type 5 | SplashKind* = enum 6 | skText, 7 | skStuiImageFormat, 8 | skANS 9 | Splash* = ref object of Controll 10 | typ*: SplashKind 11 | case kind: SplashKind 12 | of skText: 13 | lines*: seq[string] 14 | of skStuiImageFormat: 15 | frames*: seq[seq[string]] ## frames, lines (i) 66milsec sleep for 15fps 16 | header*: seq[int] 17 | ## header first line: `\e\x01 ..;..;.. \e[2K` 18 | ## width, heigth, 19 | of skANS: 20 | content*: string 21 | #lines*: seq[string] ## the image, characters only 22 | #[ colors*: seq[int] ## the colors 23 | colorMode*: int ## parsed from file ]# 24 | 25 | 26 | 27 | proc draw*(this: Splash, updateOnly: bool = false)= 28 | #if this.visible: # visibility not applies here 29 | acquire(this.app.termlock) 30 | 31 | this.app.hideControlls() #! test 32 | 33 | setColors(this.app, this.activeStyle[]) 34 | 35 | terminal_extra.clearScreen() 36 | 37 | #[ drawRect(this.leftX, 38 | this.topY, 39 | this.rightX, 40 | this.bottomY) ]# 41 | #... 42 | 43 | case this.typ: 44 | of skText: 45 | var 46 | x0 = (this.app.terminalWidth - this.width) div 2 47 | y0 = (this.app.terminalHeight - this.lines.len) div 2 48 | 49 | terminal_extra.setCursorPos(x0, y0) 50 | 51 | for L in this.lines: 52 | echo L 53 | terminal_extra.cursorForward(x0 - 1) 54 | 55 | else: discard 56 | #this.app.parkCursor() 57 | this.app.setCursorPos() 58 | 59 | release(this.app.termlock) 60 | 61 | 62 | 63 | proc drawit(this: Controll, updateOnly: bool = false) = 64 | ## *MANDATORY* called by redraw or other Controll iterators 65 | draw(Splash(this), updateOnly) 66 | 67 | 68 | 69 | 70 | proc focus(this: Controll)= 71 | this.prevStyle = this.activeStyle 72 | this.activeStyle = this.styles["input:focus"] 73 | 74 | 75 | proc blur(this: Controll)= 76 | if this.prevStyle != nil: this.activeStyle = this.prevStyle # prevstyle may not initialized 77 | 78 | proc cancel(this: Controll)=discard 79 | 80 | # made public to call if replaced - new method of event listener adding 81 | proc onClick*(this: Controll, event:KMEvent)= discard 82 | 83 | proc onDrag(this: Controll, event:KMEvent)= discard 84 | 85 | proc onDrop(this: Controll, event:KMEvent)= discard 86 | 87 | proc onKeyPress(this: Controll, event:KMEvent)= discard 88 | 89 | 90 | 91 | 92 | 93 | proc newSplash*(win:Window, kind: SplashKind): Splash = 94 | result = Splash(kind: kind) 95 | 96 | result.visible = false 97 | result.disabled = false 98 | result.width = 0 99 | 100 | result.app = win.app 101 | result.win = win 102 | result.listeners = @[] 103 | 104 | result.styles = newStyleSheets() 105 | 106 | var styleNormal: StyleSheetRef = new StyleSheetRef 107 | styleNormal.deepcopy win.app.styles["window"] 108 | result.styles.add("window",styleNormal) 109 | result.activeStyle = result.styles["window"] 110 | 111 | #[ var styleFocused: StyleSheetRef = new StyleSheetRef 112 | styleFocused.deepcopy win.app.styles["input:focus"] 113 | result.styles.add("input:focus",styleFocused) 114 | 115 | var styleDragged: StyleSheetRef = new StyleSheetRef 116 | styleDragged.deepCopy win.app.styles["input:drag"] 117 | result.styles.add("input:drag",styleDragged) 118 | 119 | var styleDisabled: StyleSheetRef = new StyleSheetRef 120 | styleDisabled.deepcopy win.app.styles["input:disabled"] 121 | result.styles.add("input:disabled", styleDisabled) ]# 122 | 123 | result.drawit = drawit 124 | 125 | # dont add to win 126 | #win.controlls.add(result) # typical finish line 127 | 128 | 129 | proc newSplash*(win:Window, str: string): Splash = 130 | result = newSplash(win, kind = skText) 131 | for L in strutils.splitLines(str): 132 | result.lines.add(L) 133 | if L.runeLen > result.width: result.width = L.runeLen 134 | 135 | 136 | 137 | 138 | 139 | 140 | proc parseEsc(splash:Splash, esc:string)= 141 | discard 142 | 143 | proc parse*(splash:Splash, str:string)= 144 | ## to get information, the splash file or string needs to be parsed... 145 | ## it can be a simple string, or string with ansi escape sequences; 146 | ## or even an animation - a multi page ansi text. 147 | ## because of the escape sequences, line width is unknown, and 148 | ## to center splash on screen, width needed. 149 | ## (heigth comes from num lines) 150 | var 151 | parsingEsc: bool = false # state 152 | esc: string 153 | currentLineLen, maxLineLen, currentFrame: int 154 | 155 | for line in lines(str): 156 | if line == "\x0C": # new frame FormFeed FF ASCII 157 | currentFrame.inc 158 | currentLineLen = 0 159 | for R in runes(line): 160 | if parsingEsc: # if we are in the middle of an escape sequence 161 | if not (($R)[0] in ['A','B','C','D','F','H','P','Q','R','S','~', 'M', 'm']): # not EOE 162 | esc &= $R 163 | continue 164 | else: 165 | esc &= $R 166 | #parseEsc(splash, esc) 167 | 168 | parsingEsc = false 169 | continue 170 | 171 | if R == '\e'.Rune: # escape, begin parsing 172 | parsingEsc = true 173 | 174 | currentLineLen.inc 175 | # END for R in runes(line) 176 | 177 | if currentLineLen > maxLineLen: maxLineLen = currentLineLen 178 | 179 | splash.frames[currentFrame].add(line) 180 | 181 | #END for line in lines(str) 182 | 183 | splash.width = maxLineLen -------------------------------------------------------------------------------- /src/stui/test_rgb256.nim: -------------------------------------------------------------------------------- 1 | import colors, colors256, colors_extra, strutils 2 | 3 | var 4 | 5 | colorNames* = [ 6 | ("aliceblue", colAliceBlue), 7 | ("antiquewhite", colAntiqueWhite), 8 | ("aqua", colAqua), 9 | ("aquamarine", colAquamarine), 10 | ("azure", colAzure), 11 | ("beige", colBeige), 12 | ("bisque", colBisque), 13 | ("black", colBlack), 14 | ("blanchedalmond", colBlanchedAlmond), 15 | ("blue", colBlue), 16 | ("blueviolet", colBlueViolet), 17 | ("brown", colBrown), 18 | ("burlywood", colBurlyWood), 19 | ("cadetblue", colCadetBlue), 20 | ("chartreuse", colChartreuse), 21 | ("chocolate", colChocolate), 22 | ("coral", colCoral), 23 | ("cornflowerblue", colCornflowerBlue), 24 | ("cornsilk", colCornsilk), 25 | ("crimson", colCrimson), 26 | ("cyan", colCyan), 27 | ("darkblue", colDarkBlue), 28 | ("darkcyan", colDarkCyan), 29 | ("darkgoldenrod", colDarkGoldenRod), 30 | ("darkgray", colDarkGray), 31 | ("darkgreen", colDarkGreen), 32 | ("darkkhaki", colDarkKhaki), 33 | ("darkmagenta", colDarkMagenta), 34 | ("darkolivegreen", colDarkOliveGreen), 35 | ("darkorange", colDarkorange), 36 | ("darkorchid", colDarkOrchid), 37 | ("darkred", colDarkRed), 38 | ("darksalmon", colDarkSalmon), 39 | ("darkseagreen", colDarkSeaGreen), 40 | ("darkslateblue", colDarkSlateBlue), 41 | ("darkslategray", colDarkSlateGray), 42 | ("darkturquoise", colDarkTurquoise), 43 | ("darkviolet", colDarkViolet), 44 | ("deeppink", colDeepPink), 45 | ("deepskyblue", colDeepSkyBlue), 46 | ("dimgray", colDimGray), 47 | ("dodgerblue", colDodgerBlue), 48 | ("firebrick", colFireBrick), 49 | ("floralwhite", colFloralWhite), 50 | ("forestgreen", colForestGreen), 51 | ("fuchsia", colFuchsia), 52 | ("gainsboro", colGainsboro), 53 | ("ghostwhite", colGhostWhite), 54 | ("gold", colGold), 55 | ("goldenrod", colGoldenRod), 56 | ("gray", colGray), 57 | ("green", colGreen), 58 | ("greenyellow", colGreenYellow), 59 | ("honeydew", colHoneyDew), 60 | ("hotpink", colHotPink), 61 | ("indianred", colIndianRed), 62 | ("indigo", colIndigo), 63 | ("ivory", colIvory), 64 | ("khaki", colKhaki), 65 | ("lavender", colLavender), 66 | ("lavenderblush", colLavenderBlush), 67 | ("lawngreen", colLawnGreen), 68 | ("lemonchiffon", colLemonChiffon), 69 | ("lightblue", colLightBlue), 70 | ("lightcoral", colLightCoral), 71 | ("lightcyan", colLightCyan), 72 | ("lightgoldenrodyellow", colLightGoldenRodYellow), 73 | ("lightgrey", colLightGrey), 74 | ("lightgreen", colLightGreen), 75 | ("lightpink", colLightPink), 76 | ("lightsalmon", colLightSalmon), 77 | ("lightseagreen", colLightSeaGreen), 78 | ("lightskyblue", colLightSkyBlue), 79 | ("lightslategray", colLightSlateGray), 80 | ("lightsteelblue", colLightSteelBlue), 81 | ("lightyellow", colLightYellow), 82 | ("lime", colLime), 83 | ("limegreen", colLimeGreen), 84 | ("linen", colLinen), 85 | ("magenta", colMagenta), 86 | ("maroon", colMaroon), 87 | ("mediumaquamarine", colMediumAquaMarine), 88 | ("mediumblue", colMediumBlue), 89 | ("mediumorchid", colMediumOrchid), 90 | ("mediumpurple", colMediumPurple), 91 | ("mediumseagreen", colMediumSeaGreen), 92 | ("mediumslateblue", colMediumSlateBlue), 93 | ("mediumspringgreen", colMediumSpringGreen), 94 | ("mediumturquoise", colMediumTurquoise), 95 | ("mediumvioletred", colMediumVioletRed), 96 | ("midnightblue", colMidnightBlue), 97 | ("mintcream", colMintCream), 98 | ("mistyrose", colMistyRose), 99 | ("moccasin", colMoccasin), 100 | ("navajowhite", colNavajoWhite), 101 | ("navy", colNavy), 102 | ("oldlace", colOldLace), 103 | ("olive", colOlive), 104 | ("olivedrab", colOliveDrab), 105 | ("orange", colOrange), 106 | ("orangered", colOrangeRed), 107 | ("orchid", colOrchid), 108 | ("palegoldenrod", colPaleGoldenRod), 109 | ("palegreen", colPaleGreen), 110 | ("paleturquoise", colPaleTurquoise), 111 | ("palevioletred", colPaleVioletRed), 112 | ("papayawhip", colPapayaWhip), 113 | ("peachpuff", colPeachPuff), 114 | ("peru", colPeru), 115 | ("pink", colPink), 116 | ("plum", colPlum), 117 | ("powderblue", colPowderBlue), 118 | ("purple", colPurple), 119 | ("red", colRed), 120 | ("rosybrown", colRosyBrown), 121 | ("royalblue", colRoyalBlue), 122 | ("saddlebrown", colSaddleBrown), 123 | ("salmon", colSalmon), 124 | ("sandybrown", colSandyBrown), 125 | ("seagreen", colSeaGreen), 126 | ("seashell", colSeaShell), 127 | ("sienna", colSienna), 128 | ("silver", colSilver), 129 | ("skyblue", colSkyBlue), 130 | ("slateblue", colSlateBlue), 131 | ("slategray", colSlateGray), 132 | ("snow", colSnow), 133 | ("springgreen", colSpringGreen), 134 | ("steelblue", colSteelBlue), 135 | ("tan", colTan), 136 | ("teal", colTeal), 137 | ("thistle", colThistle), 138 | ("tomato", colTomato), 139 | ("turquoise", colTurquoise), 140 | ("violet", colViolet), 141 | ("wheat", colWheat), 142 | ("white", colWhite), 143 | ("whitesmoke", colWhiteSmoke), 144 | ("yellow", colYellow), 145 | ("yellowgreen", colYellowGreen)] 146 | 147 | 148 | #echo parseColor( "black" , 2) 149 | 150 | for col in colorNames: 151 | try: 152 | if parseColor( col[0] , 2) == -1: 153 | echo "(\"" & col[0] & "\", )," 154 | #else: echo col[0] 155 | except: 156 | echo "E" -------------------------------------------------------------------------------- /src/stui/ui_button.nim: -------------------------------------------------------------------------------- 1 | #import stui, terminal, colors, colors_extra, colors256, unicode, tables, os, locks 2 | include "controll.inc.nim" 3 | 4 | type Button* = ref object of Controll 5 | ## no border or padding style 6 | ## use paddingH, paddingV: int in 7 | ## proc newButton*(win:Window, label: string, paddingH: int = 0, paddingV:int = 0 ): Button = 8 | ## addEventListener("click", proc(source:Controll):void) for action 9 | 10 | #label*:string 11 | paddingH, paddingV: int 12 | 13 | 14 | proc setPaddingV*(this:Button, padding:int)= 15 | this.paddingV = padding 16 | this.heigth = 1 + (padding * 2) 17 | 18 | proc setPaddingH*(this:Button, padding:int)= 19 | this.paddingH = padding 20 | this.width = this.label.runeLen() + (this.paddingH * 2) 21 | 22 | proc draw*(this: Button, updateOnly: bool = false)= 23 | if this.visible: 24 | acquire(this.app.termlock) 25 | 26 | setColors(this.app, this.activeStyle[]) 27 | 28 | #if this.activeStyle.border != "" and this.activeStyle.border != "none": 29 | #[ drawRect(this.x1 + this.activeStyle.margin.left, 30 | this.y1 + this.activeStyle.margin.top, 31 | this.x2 - this.activeStyle.margin.right, 32 | this.y2 - this.activeStyle.margin.bottom) ]# 33 | drawRect(this.leftX, 34 | this.topY, 35 | this.rightX, 36 | this.bottomY) 37 | #... 38 | var cLine = this.topY 39 | for iP in 1..this.paddingV: 40 | terminal_extra.setCursorPos(this.leftX, cLine ) 41 | stdout.write(" " * this.width) 42 | cLine += 1 43 | 44 | if this.width_value != 0: # relative width; width_value == 0 by default 45 | terminal_extra.setCursorPos(this.leftX, cLine ) 46 | 47 | this.paddingH = (this.width - this.label.runeLen) div 2 48 | stdout.write(" " * this.paddingH) 49 | stdout.write(this.label) 50 | stdout.write(" " * this.paddingH)# (this.width - this.label.runeLen - (this.paddingH * 2)) ) 51 | else: # exactly: 52 | terminal_extra.setCursorPos(this.leftX, cLine ) 53 | stdout.write(" " * this.paddingH) 54 | stdout.write(this.label) 55 | stdout.write(" " * this.paddingH) 56 | 57 | cLine += 1 58 | for iP in 1..this.paddingV: 59 | terminal_extra.setCursorPos(this.leftX, cLine ) 60 | stdout.write(" " * this.width) 61 | cLine += 1 62 | 63 | #this.app.parkCursor() 64 | this.app.setCursorPos() 65 | 66 | release(this.app.termlock) 67 | 68 | 69 | 70 | proc drawit(this: Controll, updateOnly: bool = false) = 71 | draw(Button(this), updateOnly) 72 | 73 | 74 | 75 | 76 | proc focus(this: Controll)= 77 | this.prevStyle = this.activeStyle 78 | this.activeStyle = this.styles["input:focus"] 79 | 80 | 81 | proc blur(this: Controll)= 82 | if this.prevStyle != nil: this.activeStyle = this.prevStyle # prevstyle may not initialized 83 | 84 | proc cancel(this: Controll)=discard 85 | 86 | # made public to call if replaced - new method of event listener adding 87 | proc onClick*(this: Controll, event:KMEvent) = 88 | if not this.disabled: 89 | this.app.activate(this) 90 | drawit(this) 91 | var c: char 92 | if event.evType != "CtrlKey": # mouseClick flush Release event 93 | while c != 'm': 94 | c = getch() 95 | # visual feedback: 96 | sleep(100) 97 | this.blur(this) 98 | #this.app.activate(this.win) 99 | drawit(this) 100 | trigger(this,"click") 101 | 102 | proc onDrag(this: Controll, event:KMEvent)=discard 103 | 104 | proc onDrop(this: Controll, event:KMEvent)=discard 105 | 106 | proc onKeyPress(this: Controll, event:KMEvent)= 107 | # todo: focusFWD on KeyLeft ? 108 | if not this.disabled: 109 | if event.evType == "CtrlKey": 110 | case event.ctrlKey: 111 | of 13: 112 | this.onClick(this, event) 113 | else: 114 | discard 115 | 116 | 117 | proc newButton*(win:Window, label: string, paddingH: int = 0, paddingV:int = 0 ): Button = 118 | result = new Button 119 | result.label=label 120 | result.heigth = 1 + (paddingV * 2) 121 | result.width = label.runeLen() + (paddingH * 2) # padding 122 | result.visible = false 123 | result.disabled = false 124 | result.paddingH = paddingH 125 | result.paddingV = paddingV 126 | 127 | result.app = win.app 128 | result.win = win 129 | result.listeners = @[] 130 | 131 | result.styles = newStyleSheets() 132 | 133 | var styleNormal: StyleSheetRef = new StyleSheetRef 134 | styleNormal.deepcopy win.app.styles["input"] 135 | result.styles.add("input",styleNormal) 136 | result.activeStyle = result.styles["input"] 137 | 138 | var styleFocused: StyleSheetRef = new StyleSheetRef 139 | styleFocused.deepcopy win.app.styles["input:focus"] 140 | result.styles.add("input:focus",styleFocused) 141 | 142 | var styleDragged: StyleSheetRef = new StyleSheetRef 143 | styleDragged.deepCopy win.app.styles["input:drag"] 144 | result.styles.add("input:drag",styleDragged) 145 | 146 | var styleDisabled: StyleSheetRef = new StyleSheetRef 147 | styleDisabled.deepcopy win.app.styles["input:disabled"] 148 | result.styles.add("input:disabled", styleDisabled) 149 | 150 | result.drawit = drawit 151 | result.blur = blur 152 | result.focus = focus 153 | result.onClick = onClick 154 | result.onDrag = onDrag 155 | result.onDrop = onDrop 156 | result.cancel = cancel 157 | result.onKeypress = onKeyPress 158 | 159 | win.controlls.add(result) # typical finish line 160 | 161 | proc newButton*(win:Window, label: string, width:string, paddingV:int = 0): Button = 162 | result = newButton(win, label, 0, paddingV) 163 | discard width.parseInt(result.width_value) 164 | #discard heigth.parseInt(result.heigth_value) -------------------------------------------------------------------------------- /doc/appbase.md: -------------------------------------------------------------------------------- 1 | # APPBASE 2 | 3 | Appbase is a core application boilerplate, 4 | with logging, inter-thread communication, and some support functions. 5 | 6 | (Appbase was extracted from stui, extended, genericised, and ported back) 7 | 8 | the builded applications nim.cfg controlls wich components are enabled: 9 | ``` 10 | --define:inputEventLoop_enabled # main HID event loop 11 | --define:mainChannelInt_enabled 12 | --define:mainChannelString_enabled 13 | --define:mainChannelIntTalkback_enabled # sends ptr int, change int value to talk back 14 | --define:mainChannelIntChecked_enabled # sends int and ptr Channel[int] to talk back 15 | --define:mainChannelJsonChecked_enabled # aka InterCom 16 | --define:timedActions_enabled 17 | ``` 18 | 19 | appbase/mainChannel... .inc.nim files are boilerplates for Channel handling 20 | appbase/myappbasetypes is the glue, where stui connected to appbase 21 | appbase.mainloop template runs timers, channelhandlers and eventloop 22 | 23 | stui_template.nim is derived from appbase template for stui 24 | main.inc.nim is your programs main file, wich will be included in the boilerplate 25 | 26 | ## mainChannelJsonChecked aka Intercom 27 | 28 | Its a Json channel. More precisely it is a table of Json Channels. 29 | A thread may pick a name (int), and register its channel to a feed (int). 30 | The main thread registers to [0][0] - at start. 31 | Functions like `broadcast` send messages to every feed member. 32 | `sendTo` searches for the recipient in all feeds, and sends to it exclusively. 33 | 34 | A simple test looks like this: 35 | `subscribe` send a json msg to main thread, 36 | wich registers the new channel via `mainChannelJson.inc.nim`, and sends back the errorcode: 37 | `interCom[feedname].sendTo(msgfrom, """{ "r": 0 }""")` 38 | 39 | ```nim 40 | proc testJ2*(interCom: ptr InterCom){.thread.}= 41 | let 42 | mThreadId = 31 43 | 44 | var replyJChannel31 = new ChannelJson 45 | var testJson = newJObject() 46 | testJson.add("typ", newJInt(101)) 47 | testJson.add("from", newJInt(mThreadId)) 48 | testJson.add("feed", newJInt(0)) 49 | testJson.add("chanPtr", %(addr replyJChannel31)) 50 | 51 | echo mThreadId, testJson 52 | 53 | var icom = interCom[] 54 | echo " [testJ2] subscribe result > ", icom.subscribe(mThreadId, replyJChannel31) 55 | 56 | #........... 57 | 58 | var 59 | th31: Thread[ptr InterCom] 60 | proc testJ3*()= 61 | 62 | th31.createThread(testJ2, addr interCom) 63 | ``` 64 | 65 | 66 | ##### subscribe is a little bit overloaded: 67 | it adds a channel to a feed. 68 | 69 | ```nim 70 | proc subscribe*(feed: var MainChannelJsonFeeds, thId: int, chan: var ChannelJson)= 71 | feed[thId]= chan 72 | ``` 73 | however, if used from a thread, it sends a msg to main thread (101), 74 | wich then subscribes to the feed, and sends reply. 75 | `proc subscribe*(icom: var InterCom, thId: int, chan: var ChannelJson):int=` 76 | `proc subscribe*(icom: var InterCom, feed, thId: int, chan: var ChannelJson):int=` 77 | 78 | 79 | ```nim 80 | of 101: # add a channel to intercom - a thread registers in intercom 81 | when defined(logger_enabled): notice ">>>>>> adding new thread comm channel \n", inbox.msg 82 | if msgfrom != myThId: # main channel is pre registered 83 | var feedname = jsonNode["feed"].getInt() 84 | 85 | if not interCom.hasKey(feedname): 86 | interCom.addFeed(feedname) 87 | 88 | var chan = jsonNode["chanPtr"].getChannelJson() #cast[ptr ChannelJson](cast[uint](jsonNode["chanPtr"].getInt()))[] 89 | 90 | open chan[] # [] -> chan is ref, needs [] to get the Channel 91 | interCom[feedname].subscribe( msgfrom, chan ) 92 | when defined(logger_enabled): notice ">>>>>> ", interCom.hasKey(0) 93 | interCom[feedname].sendTo(msgfrom, """{ "r": 0 }""") 94 | 95 | ``` 96 | 97 | 98 | ## mainChannelIntTalkback 99 | 100 | appbase has 'checked' or 'talkback' enabled channels for communication, 101 | so the thread can wait for operations to complete on variables, so 102 | less segfault raises. 103 | 104 | Channels are ref Channels - so they can be passed to submodules. 105 | and they need the `[]` operator to get the channel inside. 106 | plus if passed to a thread, two unboxing is needed `[][]` == `[ptr][ref]`. 107 | 108 | ```nim 109 | when defined mainChannelIntTalkback_enabled: 110 | ## a talkback channel sends a pointer to a mutable variable, eg int, 111 | ## and changes it after operation -> change can be checked, variable can be 112 | ## destroyed 113 | ## atomicInc(intPtr[]) 114 | type McitChannel* = ref Channel[ptr int] 115 | var mainChannelIntTalkback = new McitChannel 116 | proc getMcitChannel*(): McitChannel = mainChannelIntTalkback #! <-- getter ----- 117 | 118 | proc waitFor*(chan: var McitChannel, msg: var int)= 119 | var temp = msg 120 | open chan[] 121 | chan[].send(addr msg) 122 | while temp == msg: 123 | sleep(1) 124 | ``` 125 | ```nim 126 | proc handleMainChannelIntTalkback()= 127 | var inbox = tryRecv( (mainChannelIntTalkback[]) ) #tuple[dataAvailable: bool, msg: TMsg] 128 | if inbox.dataAvailable: 129 | case inbox.msg[]: 130 | of 1: 131 | # do something here 132 | atomicInc(inbox.msg[]) # talk back - release variable 133 | of 256..int.high: 134 | var msg = unpackIntMsg(inbox.msg[].uint) 135 | # do something here 136 | atomicInc(inbox.msg[]) 137 | ``` 138 | The following test uses `packIntMsg`, to store array[0..3, uint] into int. 139 | With this you are able to get a "hierarchy of messages" to decode from one simple int. 140 | ```nim 141 | proc testIntTalkBack*(mainChannelIntTalkback: ptr McitChannel){.thread.}= 142 | var 143 | msg: int 144 | resp: int 145 | chan: McitChannel 146 | 147 | chan = mainChannelIntTalkback[] 148 | msg = packIntMsg([128.uint,242.uint,9.uint,64.uint]).int 149 | open chan[] 150 | chan[].send(addr msg) 151 | resp = msg 152 | echo " {{testIntTalkBack}} Sent...", msg 153 | 154 | while resp == msg: 155 | sleep(1) 156 | echo " {{testIntTalkBack}} Receive..." 157 | echo " {{testIntTalkBack}} Received mainChannelIntTalkback" 158 | 159 | msg = packIntMsg([128.uint,242.uint,9.uint,64.uint]).int 160 | chan.waitFor(msg) 161 | echo " {{testIntTalkBack}} Received waitFor mainChannelIntTalkback" 162 | 163 | 164 | ``` 165 | 166 | ## APPBASE functions -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stui 2 | ### Simplified Terminal UI (Nim lang, ANSI terminal) 3 | 4 | This is my first big app in Nim - it covers pretty much anything i need (to learn) >:) 5 | 6 | **STUI is a drag&drop aware, responsive layout, themeable, ANSI terminal UI. (currently for linux terminals...)** 7 | 8 | **News: APPBASE is now built into STUI - or STUI is build on top of APPBASE wich brings InterCom - inter thread communications - to the table** [more on appbase ...](doc/appbase.md) 9 | 10 | branches: 11 | * master: usable alpha, revised for **nim v0.19.6** stable and **nim v0.19.9**, devel - *stable has no unicode.align == compile error; delete unicode. for nim 0.19.0 stable* 12 | * devel: work in progress 13 | 14 | releases: 15 | * nim 0.18 version archived as release 16 | 17 | (manjaro linux, visual studio code [better comments]) 18 | 19 | 20 | Status: usable alpha. missing: widgets, banners, splash; docs, cleanup 21 | see: stui_test1.nim 22 | copy&use: template_simpleapp.nim *DEPRECATED, but should be usable 23 | tested on xfce4terminal, gnome-terminal 24 | 25 | ![Screenshot_stui_test1.nim](doc/Screenshot_2018-10-03_15-23-40.png) 26 | 27 | Please help the development with your feedback. :) 28 | 29 | 30 | 31 | **News: DoubleClick enabled, LineGraph controll first alpha release =)** 32 | 33 | **Future changes: enable Style inheritance; testing for isNil may replaced with dummy procs for GC safety; MaximizableControlls (doing); .nimble fixxes** 34 | 35 | 36 | It can Tile the screen vertically, 37 | Tiles can have relative "50%" or exact "100ch" width. 38 | 39 | Windows are filling the Tile, and cascade over each other, 40 | covering each other fully - only titlebar visible ("breadcrumbs like") 41 | Windows cannot be moved, etc. 42 | 43 | Controlls layout is automatically computed - from top to bottom, left to right. 44 | Controlls can have relative or exact width / heigth 45 | or manually if Controll.recalc() is added then it sets x1,x2,y1,y2,width,heigth. 46 | 47 | Terminal resize is watched in every 2 secs - on resize layout recalculated. 48 | 49 | STUI can handle more screens - **WorkSpaces** 50 | 51 | **tree: App->WorkSpaces->Tiles->Windows->(Pages->)Controlls** 52 | 53 | PageBreak is not inserted into pages.controlls[] 54 | 55 | 56 | It can be **themed** with parseCfg compatible files *(.TSS)* style sheets 57 | 58 | 59 | **Event listeners: Observer style:** 60 | 61 | Listener = tuple[name:string, actions: seq[proc(source:Controll):void]] 62 | 63 | ListenerList = seq[Listener] 64 | 65 | proc addEventListener*(controll:Controll, evtname:string, fun:proc(source:Controll):void) 66 | 67 | proc removeEventListener*(controll:Controll, evtname:string, fun:proc(source:Controll):void) 68 | 69 | proc trigger*(controll:Controll, evtname:string ) 70 | 71 | e.g.: 72 | selectbox2.addEventListener("change", changeColorMode) 73 | 74 | proc changeColorMode(source:Controll)= 75 | discard parseInt(sb2.value, source.app.colorMode) 76 | source.app.draw() 77 | 78 | 79 | **New Demo / test file is stui_template.nim** *app template, now with appbase* 80 | **Old Demo / test file is stui_test1.nim** 81 | use F10 or 2x ESC to Quit 82 | 83 | Default Keyboard Shortcuts: 84 | F2: menu TODO 85 | F5: refresh screen 86 | F9: menu TODO 87 | F10: quit app 88 | 89 | TAB: - add focus to next gui Controll; 90 | - commit changes to Controll (e.g.:TextArea) 91 | 92 | ESC and ESC again: cancel editing, quit app 93 | 94 | PgUP/PgDown: on Window -> change Page; on TextArea: "scroll" 95 | 96 | Mouse: 97 | Wheel "Scrolls": Window->Page; TextArea, LineGraph 98 | 99 | 100 | **Dependency: like Deja-Vu TTF - a font with large unicode character set & terminal, like xfce4 terminal** 101 | ![nerd fonts](https://github.com/ryanoasis/nerd-fonts/wiki) 102 | 103 | 104 | * ui_textbox: text input, 1 line 105 | * ui_button 106 | * ui_textarea: multi line textbox, editable, scrollable 107 | * ui_menu: full screen hierarchial menu - breadcrumbs in window 108 | * ui_stringlistbox: like a listbox, items having actions, not for (multi)selection (will continue) 109 | * ui_progressbar 110 | * ui_fineprogressbar: uses unicode block characters; can be colored by level (normal, warn, err) 111 | * ui_selectbox: (multi) selectbox - it is a combined controll 112 | * ui_togglebutton: on or off :) 113 | * ui_shdbutton: button with drop shadow - just like in the good old times... 114 | * _ui_fileselect (beta): _ well it is also a sandbox for testing Controll reusability and combined controlls 115 | * _ui_linegraph (beta):_ a "bargraph", paging, scrolling works, RIGHT-CLICK TO MAXIMIZE! =) 116 | *ok...maybe ui_linegraph is a little bit complicated, maybe some day, on demand i will fork a quick&dirty version from it* 117 | 118 | ![Screenshot_2018-09-14_14-07-41](doc/Screenshot_2018-09-14_14-07-41.png) 119 | ![FileSelect](doc/FileSelect_Screenshot_2018-10-20_13-35-40.png) 120 | ![LineGraph1](doc/LineGraph1.png) 121 | ![LineGraph2](doc/LineGraph2.png) 122 | ![Screenshot_2018-09-14_14-07-18](doc/Screenshot_2018-09-14_14-07-18.png) 123 | 124 | 125 | [on colors ...](doc/Colors.md) 126 | [on Controll ...](doc/Controlls.md) 127 | 128 | 129 | **APPBASE functions** 130 | 131 | the builded applications nim.cfg controlls wich components are enabled: 132 | 133 | --define:inputEventLoop_enabled # main HID event loop 134 | --define:mainChannelInt_enabled 135 | --define:mainChannelString_enabled 136 | --define:mainChannelIntTalkback_enabled # sends ptr int, change int value to talk back 137 | --define:mainChannelIntChecked_enabled # sends int and ptr Channel[int] to talk back 138 | --define:mainChannelJsonChecked_enabled # aka InterCom 139 | --define:timedActions_enabled 140 | 141 | appbase/mainChannel... .inc.nim files are boilerplates for Channel handling 142 | appbase/myappbasetypes is the glue, where stui connected to appbase 143 | appbase.mainloop template runs timers, channelhandlers and eventloop 144 | 145 | stui_template.nim is derived from appbase template for stui 146 | main.inc.nim is your programs main file, wich will be included in the boilerplate 147 | 148 | [more on appbase ...](doc/appbase.md) 149 | 150 | 151 | 152 | I think, that even if i go back to my IOT projects, 153 | it is an easy to maintain, extend, dependency free project. 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/stui/colors_extra.nim: -------------------------------------------------------------------------------- 1 | import colors, strutils, colors256, colorsRGBto256, terminal_extra, terminal 2 | import os, osproc 3 | 4 | 5 | #type ColorTable = array[int, tuple[name: string, color:int]] 6 | 7 | const colorNames16* = [ 8 | ("fgBlack",30), 9 | ("fgRed",31), 10 | ("fgGreen",32), 11 | ("fgYellow",33), 12 | ("fgBlue",34), 13 | ("fgMagenta",35), 14 | ("fgCyan",36), 15 | ("fgWhite",37), 16 | 17 | ("bgBlack",40), 18 | ("bgRed",41), 19 | ("bgGreen",42), 20 | ("bgYellow",43), 21 | ("bgBlue",44), 22 | ("bgMagenta",45), 23 | ("bgCyan",46), 24 | ("bgWhite",47), 25 | 26 | 27 | ("fgBrightBlack",90), 28 | ("fgBrightRed",91), 29 | ("fgBrightGreen",92), 30 | ("fgBrightYellow",93), 31 | ("fgBrightBlue",94), 32 | ("fgBrightMagenta",95), 33 | ("fgBrightCyan",96), 34 | ("fgBrightWhite",97), 35 | 36 | ("bgBrightBlack",100), 37 | ("bgBrightRed",101), 38 | ("bgBrightGreen",102), 39 | ("bgBrightYellow",103), 40 | ("bgBrightBlue",104), 41 | ("bgBrightMagenta",105), 42 | ("bgBrightCyan",106), 43 | ("bgBrightWhite",107) 44 | ] 45 | 46 | 47 | type 48 | PackedRGB* = distinct int32 49 | Color16* = distinct int 50 | 51 | proc extractRGB*(a: int): tuple[r, g, b: range[0..255]] = 52 | ## extracts the red/green/blue components of the color `a`. 53 | result.r = a.int shr 16 and 0xff 54 | result.g = a.int shr 8 and 0xff 55 | result.b = a.int and 0xff 56 | proc extractRGB*(a: PackedRGB): tuple[r, g, b: range[0..255]] = extractRGB(int(a)) 57 | 58 | 59 | proc packRGB*(r,g,b:int): PackedRGB = 60 | ## pack r,g,b values into 1 int32, PackedRGB 61 | result = PackedRGB(r shl 16 or g shl 8 or b) 62 | proc packRGB*(a:seq[string]): PackedRGB = 63 | ## pack r,g,b values into 1 int32, PackedRGB 64 | var 65 | r,g,b:int 66 | 67 | r = parseInt(a[0]) 68 | g = parseInt(a[1]) 69 | b = parseInt(a[2]) 70 | 71 | result = PackedRGB(r shl 16 or g shl 8 or b) 72 | 73 | proc `$`*(a: PackedRGB): string = 74 | $int(a) 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | proc searchColorTable*(colorTable: openArray[tuple[name: string, col: int]], 86 | colorName: string): int = 87 | #echo colorTable.high 88 | result = -1 #int.high 89 | for i in 0..colorTable.high: 90 | if toLowerAscii(colorTable[i].name) == toLowerAscii(colorName): return colorTable[i].col 91 | #echo i 92 | 93 | 94 | #[ proc getColorMode(): int = 95 | var str = getEnv("COLORTERM") #$execProcess("printenv COLORTERM") 96 | if str notin ["truecolor", "24bit"]: 97 | str = $execProcess("tput colors") 98 | 99 | case str: 100 | of "8": result = 0 101 | of "16": result = 1 102 | of "256": result = 2 103 | else: result = 1 104 | else: 105 | result = 3 ]# 106 | 107 | 108 | proc parseColor*(colorName: string, colorMode: int): int {.gcsafe.}= 109 | ## searches for colors int value by name 110 | ## you better search for valid color names... ;) 111 | case colorMode: 112 | of 0,1: result = searchColorTable(colorNames16, colorName) #colorNames16[ searchColorTable(colorNames16, colorName) ][1] 113 | of 2: 114 | result = searchColorTable(colorNames256, colorName) #colorNames256[ searchColorTable(colorNames256, colorName) ][1] 115 | if result == -1: 116 | result = searchColorTable(colorNamesRGBto256, colorName) 117 | of 3: 118 | try: 119 | result = int(colors.parseColor( toLowerAscii(colorName))) 120 | except: 121 | result = 0 122 | else: result = 0 123 | 124 | proc parseColor*(colorName: string): int = parseColor(colorName, getColorMode()) 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | # 16 colors 8 + bright... 137 | # "Later terminals added the ability to directly specify the "bright" colors with 90-97 and 100-107. " 138 | # but for me, only adding styleBright gives good results 139 | proc setForegroundColor*(f: File, col: Color16) = 140 | f.write("\e[" & $int(col) & "m") 141 | 142 | proc setForegroundColor*(col: Color16) = 143 | setForegroundColor(stdout, col) 144 | 145 | proc setBackgroundColor*(f: File, col: Color16) = 146 | f.write("\e[" & $int(col) & "m") 147 | 148 | proc setBackgroundColor*(col: Color16) = 149 | setForegroundColor(stdout, col) 150 | 151 | 152 | 153 | # Color256 Color256 Color256 Color256 Color256 Color256 154 | proc setBackgroundColor*(f: File, col: Color256) = 155 | f.write("\e[48;5;" & $int(col) & "m") 156 | 157 | proc setBackgroundColor*(col: Color256) = 158 | setBackgroundColor(stdout, col) 159 | 160 | proc setForegroundColor*(f: File, col: Color256) = 161 | f.write("\e[38;5;" & $int(col) & "m") 162 | 163 | proc setForegroundColor*(col: Color256) = 164 | setForegroundColor(stdout, col) 165 | 166 | 167 | 168 | # RGB colors RGB RGB RGB RGB RGB RGB RGB RGB RGB 169 | proc setForegroundColor*(f: File, col: PackedRGB) = 170 | let color = extractRGB(col) 171 | f.write("\e[38;2;" & $color.r & ";" & $color.g & ";" & $color.b & "m") 172 | #echo color , (" x1b[38;2;" & $color.r & ";" & $color.g & ";" & $color.b & "m") 173 | 174 | proc setForegroundColor*(col: PackedRGB) = 175 | setForegroundColor(stdout, col) 176 | 177 | 178 | proc setBackgroundColor*(f: File, col: PackedRGB) = 179 | let color = extractRGB(col) 180 | f.write("\e[48;2;" & $color.r & ";" & $color.g & ";" & $color.b & "m") 181 | #echo color, col 182 | 183 | proc setBackgroundColor*(col: PackedRGB) = 184 | setBackgroundColor(stdout, col) 185 | #echo "AAAA", col 186 | 187 | #................................ 188 | 189 | proc setForegroundColor*(colorname: string){.gcsafe.} = 190 | let cmode = getColorMode() 191 | let color = colors_extra.parseColor(colorname, cmode) 192 | case cmode: 193 | of 0,1: 194 | #colors_extra.setBackgroundColor(Color16(style.bgColor[colorMode])) 195 | colors_extra.setForegroundColor(Color16(color)) 196 | of 2: 197 | #colors_extra.setBackgroundColor(Color256(style.bgColor[colorMode])) 198 | colors_extra.setForegroundColor(Color256(color)) 199 | of 3: 200 | #colors_extra.setBackgroundColor(PackedRGB(style.bgColor[colorMode])) 201 | colors_extra.setForegroundColor(PackedRGB(color)) 202 | else: discard 203 | 204 | 205 | #............................................................................... 206 | 207 | 208 | 209 | when isMainModule:#------------------------------------------------ 210 | echo packRGB(214,200,113) 211 | echo extractRGB(packRGB(214,200,113)) 212 | -------------------------------------------------------------------------------- /tests/template/stui_template.nim: -------------------------------------------------------------------------------- 1 | ## template for appbase library 2 | ## copy-paste, save-as, customize :-) 3 | 4 | import stui/appbase/appbase # this comes from pkg 5 | import appbase/myappbasetypes # this comes from users subdir 6 | import threadpool, tables, os 7 | ## on appbase/myappbasetypes: here, appbase is the subdirectory, not the pkg! 8 | 9 | when defined logger_enabled: 10 | #! it declares a logger - override it as needed <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 11 | include "appbase/logger.inc.nim" 12 | 13 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14 | #! ADD imports HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 15 | 16 | import stui, terminal, colors, threadpool, os, ospaths, tables, locks, parsecfg 17 | 18 | import stui/[colors_extra, terminal_extra, kmloop] 19 | import stui/[ui_textbox, ui_button, ui_textarea, ui_stringlistbox] 20 | 21 | import strformat, unicode, strutils, parseutils, random, times 22 | 23 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 24 | #! app init defaults <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 25 | 26 | var app: MyApp ## (i) from your myappbasetypes 27 | app = newApp(splitPath(currentSourcePath()).head ) # (path to themes) 28 | 29 | app.threadId = 0 ## convention. 30 | ## replaced getThreadId(), because a Thread does not knows it, 31 | ## and app is not GCsafe to get it from... 32 | 33 | app.quit = proc(errorcode: int = QuitSuccess) = 34 | withLock app.termlock: # don't write on terminal 35 | app.flags[int(quitFlag)] = 1 # thread loop break 36 | sleep(100) # grace time 37 | system.quit(errorcode) 38 | 39 | proc appQuit() = 40 | app.quit(QuitSuccess) 41 | #............................................................................... 42 | 43 | proc checkTerminalResized()= 44 | ## used as/via TimedAction action 45 | ## used internally 46 | if app.terminalHeight != terminalHeight() or app.terminalWidth != terminalWidth(): 47 | app.terminalHeight = terminalHeight() 48 | app.terminalWidth = terminalWidth() 49 | app.recalc() 50 | for iws in app.workSpaces: 51 | for it in iws.tiles: 52 | for iw in it.windows: 53 | if iw.currentPage > iw.pages.high: iw.currentPage = iw.pages.high 54 | app.draw() 55 | 56 | var 57 | rT:TimedAction=( 58 | name:"termresize", 59 | interval: 2.float, 60 | action: nil, #O.o: adding checkTerminalResized here results error 61 | lastrun:epochTime() 62 | ) 63 | 64 | rT.action = checkTerminalResized 65 | app.timers.add(rT) 66 | 67 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 68 | 69 | #! INITIALIZE EVENT HANDLERS HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 70 | #! appbase.mainloop calls Channels handlers see appbase folder for templates 71 | #! SIMPLE InterCom - thread to main communication: 72 | 73 | when defined mainChannelString_enabled: include "appbase/mainChannelString.inc.nim" 74 | #------------------------------------------------------------------------------- 75 | when defined mainChannelInt_enabled: include "appbase/mainChannelInt.inc.nim" 76 | #------------------------------------------------------------------------------- 77 | when defined mainChannelIntChecked_enabled: include "appbase/mainChannelIntChecked.inc.nim" 78 | #------------------------------------------------------------------------------- 79 | when defined mainChannelIntTalkback_enabled: include "appbase/mainChannelIntTalkback.inc.nim" 80 | #------------------------------------------------------------------------------- 81 | #! INTERCOM - inter-thread capable communication: 82 | when defined mainChannelJsonChecked_enabled: 83 | import json 84 | include "appbase/mainChannelJson.inc.nim" 85 | 86 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 87 | 88 | #! INCLUDE GUI INIT HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 89 | include "main.inc.nim" 90 | 91 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 92 | 93 | #! a good place to add some tests ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94 | #! end of a good place to add some tests ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 95 | 96 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97 | 98 | #! RUN ☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰ 99 | #! terminal init and app starts to run here 100 | #! create gui controlls BEFORE this 101 | 102 | app.initTerminal() 103 | app.recalc() 104 | app.draw() 105 | 106 | #! MAIN LOOP-------------------------------------------------------------------- 107 | 108 | var inputLoopEvent: InputLoopEvent #! (i) from myappbasetypes 109 | var inputLoopEventFlowVar: FlowVar[InputLoopEvent] 110 | 111 | #! MAIN LOOP STARTS HERE: 112 | when defined inputEventLoop_enabled: 113 | inputLoopEventFlowVar = spawn kmLoop() #! REPLACE PROC WITH YOURS #1/2 <<<<<<< 114 | 115 | app.mainLoop: 116 | ##! 'here' appbase.mainloop calls Channels handlers see above ^ 117 | ## ........................................................................... 118 | ##! GET EVENT OBJECT: (only runs if inputLoopEventFlowVar.isReady) 119 | inputLoopEvent = InputLoopEvent(^inputLoopEventFlowVar) # it stops here anyway... 120 | 121 | #! PROCESS MESSAGE HERE -- case inputLoopEvent, of x: etc 122 | 123 | case inputLoopEvent.evType: 124 | of "Click","Release","Drag","Drop", "ScrollUp","ScrollDown", "DoubleClick": 125 | app.mouseEventHandler(inputLoopEvent) 126 | 127 | of "FnKey","CtrlKey", "Char": # vscode terminal middle mouse click triggers this too... 128 | # KeyPgUp, KeyPgDown trigger controlls cb first then apps 129 | if inputLoopEvent.key in [KeyPgUp, KeyPgDown]: 130 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 131 | app.activeControll.onKeypress(app.activeControll, inputLoopEvent) 132 | elif app.onKeypress != nil: 133 | discard app.onKeypress(app, inputLoopEvent) 134 | else: # trigger apps cb first, then controlls 135 | if app.onKeypress != nil and app.onKeypress(app, inputLoopEvent) == false: 136 | # if app not handles this, then try controlls 137 | if app.activeControll != nil and app.activeControll.onKeypress != nil: 138 | app.activeControll.onKeypress(app.activeControll, inputLoopEvent) 139 | 140 | of "EXIT": 141 | if app.activeControll != nil: 142 | try: 143 | if app.activeControll.cancel != nil: 144 | app.activeControll.cancel(app.activeControll) 145 | app.activeControll = nil 146 | except: 147 | break # == quit 148 | else: 149 | break # == quit 150 | else: discard 151 | 152 | ##! RESTART LOOP: 153 | inputLoopEventFlowVar = spawn kmLoop() #! REPLACE PROC WITH YOURS #2/2 <<<<<<< 154 | -------------------------------------------------------------------------------- /src/stui/ui_progressbar.nim: -------------------------------------------------------------------------------- 1 | include "controll.inc.nim" 2 | #import stui, terminal, colors, colors_extra, unicode, tables, parseutils, locks 3 | 4 | type ProgressBar* = ref object of Controll 5 | #label*:string 6 | val*:int 7 | preval*:int # undo 8 | 9 | #size*:int # of input 10 | showValue*:bool 11 | 12 | runeBlock*, runeEmpty*:string # what will be printed on screen 13 | 14 | 15 | 16 | 17 | 18 | proc draw*(this: ProgressBar, updateOnly: bool = false) = 19 | if this.visible: 20 | acquire(this.app.termlock) 21 | 22 | #stdout.write "\e[?25l" 23 | hideCursor() 24 | 25 | if not updateOnly: 26 | setColors(this.app, this.win.activeStyle[]) 27 | terminal_extra.setCursorPos(this.x1 + this.activeStyle.margin.left, 28 | this.y1 + this.activeStyle.margin.top) 29 | stdout.write this.label 30 | 31 | 32 | # draw border 33 | drawBorder(this.activeStyle.border, 34 | this.x1 + this.activeStyle.margin.left, 35 | this.y1 + this.activeStyle.margin.top + 1 #[label]#, 36 | this.x2 - this.activeStyle.margin.right, 37 | this.y2 - this.activeStyle.margin.bottom 38 | ) 39 | #... 40 | 41 | # echo input area 42 | 43 | stui.setColors(this.app, this.activeStyle[]) 44 | terminal_extra.setCursorPos(this.leftX(), 45 | this.bottomY()) 46 | # █ ▓ ░ 47 | var size: int = int((this.width / 100) * float(this.val)) 48 | 49 | #! about '\n': 50 | #! Stream Type Behavior 51 | #! stdin input line-buffered 52 | #! stdout (TTY) output line-buffered 53 | #! stdout (not a TTY) output fully-buffered 54 | #! stderr output unbuffered 55 | 56 | if size > 0 : 57 | #stdout.write $size 58 | stdout.write this.runeBlock * size #"▓" * size 59 | stdout.write this.runeEmpty * (this.width - size) #" " * (this.width - size) 60 | stdout.write '\n' 61 | else: 62 | #stdout.write $size 63 | stdout.write " " * this.width 64 | stdout.write '\n' 65 | 66 | 67 | if this.showValue : 68 | terminal_extra.setCursorPos(this.x2 - this.activeStyle.margin.right - 4, 69 | this.y1 + this.activeStyle.margin.top) 70 | 71 | stdout.write "████" # clear prev value 72 | terminal_extra.setCursorPos(this.x2 - this.activeStyle.margin.right - 4, 73 | this.y1 + this.activeStyle.margin.top) 74 | 75 | terminal_extra.setReversed() 76 | 77 | if this.val < 10: 78 | terminal_extra.cursorForward(2) 79 | elif this.val < 100 : 80 | terminal_extra.cursorForward(1) 81 | 82 | stdout.write $this.val & "%" 83 | 84 | #this.app.parkCursor() 85 | this.app.setCursorPos() 86 | #stdout.write "\e[?25h" 87 | showCursor() 88 | release(this.app.termlock) 89 | 90 | 91 | ### MANDATORY ### 92 | proc drawit(this: Controll, updateOnly: bool = false) = 93 | draw(ProgressBar(this), updateOnly) 94 | 95 | 96 | 97 | 98 | 99 | proc `value=`*(this: ProgressBar, str:string) = 100 | discard parseInt(str, this.val) 101 | if this.visible: this.draw() 102 | trigger(this, "change") 103 | 104 | proc `value`*(this: ProgressBar): string = $this.val 105 | 106 | # Textbox value and value2 same type as val : string 107 | proc `value2=`*(this: ProgressBar, val:int) = 108 | this.val = val 109 | if this.visible: this.draw() 110 | trigger(this, "change") 111 | 112 | proc `value2`*(this: ProgressBar): int = this.val 113 | 114 | 115 | 116 | 117 | 118 | proc focus(this: Controll)= 119 | this.prevStyle = this.activeStyle 120 | this.activeStyle = this.styles["input:focus"] 121 | 122 | 123 | 124 | proc blur(this: Controll)= 125 | if this.prevStyle != nil: # prevstyle may not initialized 126 | this.activeStyle = this.prevStyle 127 | 128 | 129 | 130 | 131 | proc cancel(this: Controll)= 132 | ProgressBar(this).val = 0 133 | ProgressBar(this).blur(this) 134 | ProgressBar(this).draw() 135 | 136 | 137 | proc onClick(this: Controll, event: KMEvent):void= 138 | if not this.disabled: 139 | trigger(this, "click") 140 | ProgressBar(this).draw() 141 | #[ 142 | proc onKeyPress(this: Controll, event: KMEvent)= 143 | discard 144 | 145 | 146 | 147 | 148 | 149 | proc onDrop(this: Controll, event: KMEvent):void= 150 | discard 151 | 152 | 153 | proc onDrag(this: Controll, event: KMEvent)= 154 | discard 155 | ]# 156 | 157 | 158 | 159 | 160 | 161 | 162 | ######## # # ###### # # 163 | ######## ## # # # # 164 | ######## # # # ##### # # 165 | ######## # # # # # ## # 166 | ######## # ## # ## ## 167 | ######## # # ###### # # 168 | 169 | proc newProgressBar*(win:Window, label: string, width:int=20, showValue: bool = false): ProgressBar = 170 | result = new ProgressBar 171 | result.label=label 172 | result.showValue = showValue 173 | result.runeBlock = "▓" 174 | result.runeEmpty = " " 175 | 176 | result.visible = false 177 | result.disabled = false 178 | 179 | result.width = width 180 | result.heigth = 2 181 | result.styles = newStyleSheets() #newTable[string, StyleSheetRef](8) 182 | 183 | var styleNormal: StyleSheetRef = new StyleSheetRef 184 | styleNormal.deepcopy win.app.styles["input:inverse"] 185 | result.styles.add("input",styleNormal) 186 | 187 | var styleFocused: StyleSheetRef = new StyleSheetRef 188 | styleFocused.deepcopy win.app.styles["input:focus"] 189 | result.styles.add("input:focus",styleFocused) 190 | 191 | var styleDragged: StyleSheetRef = new StyleSheetRef 192 | styleDragged.deepCopy win.app.styles["input:drag"] 193 | result.styles.add("input:drag",styleDragged) 194 | 195 | var styleDisabled: StyleSheetRef = new StyleSheetRef 196 | styleDisabled.deepcopy win.app.styles["input:disabled"] 197 | result.styles.add("input:disabled", styleDisabled) 198 | 199 | result.activeStyle = result.styles["input"] 200 | 201 | 202 | result.drawit = drawit 203 | result.blur = blur 204 | result.focus = focus 205 | result.cancel = cancel 206 | #[ result.onClick = onClick 207 | result.onDrag = onDrag 208 | result.onDrop = onDrop 209 | result.onKeypress = onKeyPress ]# 210 | 211 | result.app = win.app 212 | result.win = win 213 | result.listeners = @[] 214 | 215 | win.controlls.add(result) 216 | 217 | 218 | proc newProgressBar*(win:Window, label: string, width:string, showValue: bool = false): ProgressBar = 219 | result = newProgressBar(win, label, width = 0, showValue) 220 | discard width.parseInt(result.width_value) -------------------------------------------------------------------------------- /src/stui/ui_shdbutton.nim: -------------------------------------------------------------------------------- 1 | #import stui, terminal, colors, colors_extra, colors256, unicode, tables, os, locks 2 | include "controll.inc.nim" 3 | 4 | type ShdButton* = ref object of Controll 5 | ## no border or padding style 6 | ## use paddingH, paddingV: int in 7 | ## proc newButton*(win:Window, label: string, paddingH: int = 0, paddingV:int = 0 ): Button = 8 | ## addEventListener("click", proc(source:Controll):void) for action 9 | 10 | #label*:string 11 | paddingH, paddingV: int 12 | 13 | 14 | proc setPaddingV*(this:ShdButton, padding:int)= 15 | this.paddingV = padding 16 | this.heigth = 2 + (padding * 2) 17 | 18 | proc setPaddingH*(this:ShdButton, padding:int)= 19 | this.paddingH = padding 20 | this.width = this.label.runeLen() + (this.paddingH * 2) 21 | 22 | proc draw*(this: ShdButton, updateOnly: bool = false)= 23 | if this.visible: 24 | acquire(this.app.termlock) 25 | 26 | setColors(this.app, this.activeStyle[]) 27 | 28 | #if this.activeStyle.border != "" and this.activeStyle.border != "none": 29 | #[ drawRect(this.x1 + this.activeStyle.margin.left, 30 | this.y1 + this.activeStyle.margin.top, 31 | this.x2 - this.activeStyle.margin.right, 32 | this.y2 - this.activeStyle.margin.bottom) ]# 33 | drawRect(this.leftX, 34 | this.topY, 35 | this.rightX, 36 | this.bottomY - 1) 37 | #... 38 | var cLine = this.topY 39 | for iP in 1..this.paddingV: 40 | terminal_extra.setCursorPos(this.leftX, cLine ) 41 | stdout.write(" " * this.width) 42 | cLine += 1 43 | 44 | if this.width_value != 0: # relative width; width_value == 0 by default 45 | terminal_extra.setCursorPos(this.leftX, cLine ) 46 | 47 | this.paddingH = (this.width - this.label.runeLen) div 2 48 | stdout.write(" " * this.paddingH) 49 | stdout.write(this.label) 50 | stdout.write(" " * (this.width - this.label.runeLen - (this.paddingH * 2)) ) 51 | else: # exactly: 52 | terminal_extra.setCursorPos(this.leftX, cLine ) 53 | stdout.write(" " * this.paddingH) 54 | stdout.write(this.label) 55 | stdout.write(" " * this.paddingH) 56 | 57 | cLine += 1 58 | for iP in 1..this.paddingV: 59 | terminal_extra.setCursorPos(this.leftX, cLine ) 60 | stdout.write(" " * this.width) 61 | cLine += 1 62 | 63 | 64 | setColors(this.app, this.win.activeStyle[]) 65 | case this.app.colorMode: 66 | of 0,1: colors_extra.setForegroundColor(Color16(90)) 67 | of 2: colors_extra.setForegroundColor(Color256(236)) 68 | else #[ of 3 ]#: colors_extra.setForegroundColor("dimgray") 69 | 70 | terminal_extra.setCursorPos(this.leftX, cLine ) 71 | stdout.write("▝") 72 | stdout.write("▀" * (this.width - 1)) 73 | stdout.write("▘") 74 | cLine = this.topY 75 | terminal_extra.setCursorPos(this.rightX + 1, cLine ) 76 | stdout.write("▖") 77 | if this.heigth > 2: 78 | for iY in 1..this.heigth - 2: 79 | cLine += 1 80 | terminal_extra.setCursorPos(this.rightX + 1, cLine ) 81 | stdout.write("▌") 82 | 83 | 84 | #this.app.parkCursor() 85 | this.app.setCursorPos() 86 | 87 | release(this.app.termlock) 88 | 89 | 90 | 91 | proc drawit(this: Controll, updateOnly: bool = false) = 92 | draw(ShdButton(this), updateOnly) 93 | 94 | 95 | 96 | 97 | proc focus(this: Controll)= 98 | this.prevStyle = this.activeStyle 99 | this.activeStyle = this.styles["input:focus"] 100 | 101 | 102 | proc blur(this: Controll)= 103 | if this.prevStyle != nil: this.activeStyle = this.prevStyle # prevstyle may not initialized 104 | 105 | proc cancel(this: Controll)=discard 106 | 107 | # made public to call if replaced - new method of event listener adding 108 | proc onClick*(this: Controll, event:KMEvent) = 109 | if not this.disabled: 110 | #this.focus(this) - it is already focused 111 | #drawit: 112 | withLock this.app.termlock: 113 | setColors(this.app, this.win.activeStyle[]) 114 | colors_extra.setForegroundColor("orange") 115 | 116 | var cLine = this.topY 117 | terminal_extra.setCursorPos(this.leftX, cLine ) 118 | stdout.write("▗") 119 | stdout.write("▄" * (this.width - 1)) 120 | stdout.write("▖") 121 | 122 | if this.heigth > 2: 123 | for iY in 1..this.heigth - 2: 124 | cLine += 1 125 | terminal_extra.setCursorPos(this.leftX, cLine ) 126 | stdout.write("▐") 127 | stdout.write("█" * (this.width - 1)) 128 | stdout.write("▌") 129 | 130 | cLine += 1 131 | terminal_extra.setCursorPos(this.leftX, cLine ) 132 | stdout.write("▝") 133 | stdout.write("▀" * (this.width - 1)) 134 | stdout.write("▘") 135 | 136 | var c: char 137 | if event.evType != "CtrlKey": # mouseClick flush Release event 138 | while c != 'm': 139 | c = getch() 140 | # visual feedback: 141 | sleep(100) 142 | this.blur(this) 143 | drawit(this) 144 | trigger(this,"click") 145 | 146 | proc onDrag(this: Controll, event:KMEvent)=discard 147 | 148 | proc onDrop(this: Controll, event:KMEvent)=discard 149 | 150 | proc onKeyPress(this: Controll, event:KMEvent)= 151 | # todo: focusFWD on KeyLeft ? 152 | if not this.disabled: 153 | if event.evType == "CtrlKey": 154 | case event.ctrlKey: 155 | of 13: 156 | this.onClick(this, event) 157 | else: 158 | discard 159 | 160 | 161 | proc newShdButton*(win:Window, label: string, paddingH: int = 0, paddingV:int = 0 ): ShdButton = 162 | result = new ShdButton 163 | result.label=label 164 | result.heigth = 2 + (paddingV * 2) 165 | result.width = label.runeLen() + (paddingH * 2) # padding 166 | result.visible = false 167 | result.disabled = false 168 | result.paddingH = paddingH 169 | result.paddingV = paddingV 170 | 171 | result.app = win.app 172 | result.win = win 173 | result.listeners = @[] 174 | 175 | result.styles = newStyleSheets() 176 | 177 | var styleNormal: StyleSheetRef = new StyleSheetRef 178 | styleNormal.deepcopy win.app.styles["input"] 179 | result.styles.add("input",styleNormal) 180 | result.activeStyle = result.styles["input"] 181 | 182 | var styleFocused: StyleSheetRef = new StyleSheetRef 183 | styleFocused.deepcopy win.app.styles["input:focus"] 184 | result.styles.add("input:focus",styleFocused) 185 | 186 | var styleDragged: StyleSheetRef = new StyleSheetRef 187 | styleDragged.deepCopy win.app.styles["input:drag"] 188 | result.styles.add("input:drag",styleDragged) 189 | 190 | var styleDisabled: StyleSheetRef = new StyleSheetRef 191 | styleDisabled.deepcopy win.app.styles["input:disabled"] 192 | result.styles.add("input:disabled", styleDisabled) 193 | 194 | result.drawit = drawit 195 | result.blur = blur 196 | result.focus = focus 197 | result.onClick = onClick 198 | result.onDrag = onDrag 199 | result.onDrop = onDrop 200 | result.cancel = cancel 201 | result.onKeypress = onKeyPress 202 | 203 | win.controlls.add(result) # typical finish line 204 | 205 | proc newShdButton*(win:Window, label: string, width:string, paddingV:int = 0): ShdButton = 206 | result = newShdButton(win, label, 0, paddingV) 207 | discard width.parseInt(result.width_value) 208 | #discard heigth.parseInt(result.heigth_value) -------------------------------------------------------------------------------- /src/stui/kmloop.nim: -------------------------------------------------------------------------------- 1 | import terminal, parseutils, threadpool, os, times, stui, colors_extra, terminal_extra 2 | 3 | 4 | 5 | #[ type 6 | MouseCallback = proc (app: App, mEv: MouseEvent) {.closure.} 7 | EscapeCallback = proc (app: App, kEv: KeyEvent) {.closure.} 8 | CharCallback = proc (app: App, kEv: KeyEvent) {.closure.} ]# 9 | 10 | 11 | 12 | var 13 | mouse_state: int8 = -1 14 | #[ 15 | 10: 1 click 16 | 11: 1 release 17 | 12: 1 drag 18 | 19 | 20,20,22: btn2 middle 20 | 30,31,32: btn3 right 21 | ]# 22 | 23 | clickEpoch: float 24 | 25 | 26 | # problem with doubleclik/single click sends before... 27 | proc mouse_parser*(mstring: var string, c: var char): KMEvent {.inline.}= 28 | var 29 | mbtn, mx, my: int 30 | 31 | result = new KMEvent 32 | 33 | var cursor : int = 0 34 | var pstr : string = "" 35 | cursor.inc parseUntil(mstring, pstr, ';', cursor) 36 | cursor.inc 37 | discard parseInt(pstr, mbtn, 0) 38 | cursor.inc parseUntil(mstring, pstr, ';', cursor) 39 | cursor.inc 40 | discard parseInt(pstr, mx, 0) 41 | cursor.inc parseUntil(mstring, pstr, ';', cursor) 42 | cursor.inc 43 | discard parseInt(pstr, my, 0) 44 | 45 | 46 | #proc mouse_cb(mbtn, mx, my :int, c:char) = 47 | 48 | 49 | let clickInterval = epochTime() - clickEpoch 50 | 51 | case mbtn: 52 | of 0,1,2: 53 | if c == 'M': 54 | #clickM += 1 55 | result.evType = "Click" 56 | if clickEpoch == 0: 57 | clickEpoch = epochTime() 58 | #echo " c " & $clickEpoch 59 | else: 60 | #echo " " & $clickInterval 61 | if clickInterval < 0.4 : 62 | #echo "DoubleClick " & $clickInterval 63 | result.evType = "DoubleClick" 64 | clickEpoch = 0 65 | else: 66 | clickEpoch = epochTime() 67 | 68 | elif c == 'm' : 69 | result.evType = "Release" 70 | #clickM2 += 1 71 | if clickEpoch != 0 :#and clickM == clickM2: 72 | if clickInterval > 1.3: 73 | #echo "LOMGLCLICK " & $clickInterval 74 | result.evType = "LongClick" 75 | clickEpoch = 0 76 | 77 | if mouse_state == 1: 78 | mouse_state = 2 #drop 79 | result.evType = "Drop" 80 | else: 81 | mouse_state = 0 #reset 82 | 83 | of 32,33,34: 84 | if mouse_state != 3: 85 | mouse_state = 1 #drag-1 if not cancelled-3 86 | terminal_extra.setCursorPos(mx, my) 87 | #terminal.setForegroundColor(fgMagenta,true) 88 | colors_extra.setForegroundColor(Color16(fgMagenta)) 89 | stdout.write("◉") # • 90 | result.evType = "Drag" 91 | clickEpoch = 0 92 | 93 | of 65: 94 | result.evType = "ScrollDown" 95 | of 64: 96 | result.evType = "ScrollUp" 97 | 98 | else: discard 99 | 100 | 101 | #echo "btn: " , mbtn , " X: " , mx , " Y: " , my , if c == 'M': " pressed" else: " release" 102 | 103 | #case mouse_state: 104 | # of 1: echo "drag" 105 | # of 2: echo "drop" 106 | # else: discard 107 | 108 | 109 | result.c = c 110 | result.btn = mbtn 111 | result.x = mx 112 | result.y = my 113 | 114 | # CALL TARGETOBJ.onClick(mEvent) --------------- 115 | 116 | #----------------------------------- 117 | # todo: maybe a enum to interpret? 118 | proc esc_parser(str:string): KMEvent {.inline.}= 119 | #echo "esc: ", str 120 | result = new KMEvent 121 | result.key = str 122 | result.evType = "FnKey" 123 | 124 | proc char_parser(str:string): KMEvent {.inline.}= 125 | if str.len == 1 and (ord(str[0]) < 32 or ord(str[0]) == 127): #1-32, 127 control char 126 | #echo "ctrl: ", ord(str[0]) 127 | #var res = new CtrlKeyEvent 128 | result = new KMEvent 129 | result.key = str 130 | result.ctrlKey = ord(str[0]) 131 | result.evType = "CtrlKey" 132 | return result 133 | else: 134 | #echo "char: ", str 135 | result = new KMEvent 136 | result.key = str 137 | result.evType = "Char" 138 | if mouse_state == 1: 139 | #echo "cancel drag op" 140 | mouse_state = 0 141 | 142 | 143 | 144 | 145 | 146 | # 147 | # ███╗ ███╗ █████╗ ██╗███╗ ██╗ 148 | # ████╗ ████║██╔══██╗██║████╗ ██║ 149 | # ██╔████╔██║███████║██║██╔██╗ ██║ 150 | # ██║╚██╔╝██║██╔══██║██║██║╚██╗██║ 151 | # ██║ ╚═╝ ██║██║ ██║██║██║ ╚████║ 152 | # ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ 153 | # 154 | 155 | proc kmLoop*(): KMEvent = 156 | var c: char 157 | var str: string = "" 158 | block tobreak: 159 | #while true: 160 | str = "" 161 | c = getch() 162 | if c == '\e': 163 | #str = str & "esc:" 164 | c = getch() 165 | str = str & c 166 | if c == '\e': # else CSI: [ 167 | #break tobreak 168 | result=new KMEvent 169 | result.evType = "EXIT" 170 | return result 171 | c = getch() # CSI begins here 172 | 173 | if c == '<': # mouse event 174 | #var mbtn, mx, my:int 175 | str = "" # reset to get parsable string 176 | while not (c in ['M', 'm']) : #read mouse 'til end 177 | c = getch() 178 | if c != 'M' and c != 'm' : str = str & c 179 | result = mouse_parser(str, c) 180 | #mouse_cb(app, mEv ) 181 | return result 182 | 183 | 184 | else: # function key 185 | while not (c in ['A','B','C','D','F','H','P','Q','R','S','~', 'M']) : 186 | str = str & c 187 | c = getch() 188 | str = str & c 189 | #echo str 190 | result = esc_parser(str) 191 | #esc_cb(app, esc_parser(str) ) 192 | return result 193 | #str = "" 194 | else : 195 | str = str & c 196 | #echo uint8(c) shr 7 197 | # https://zaemis.blogspot.com/2011/06/reading-unicode-utf-8-in-c.html 198 | var utf8len : int = 0 199 | if (uint8(c) and 192) == 192 : utf8len.inc 200 | if (uint8(c) and 224) == 224 : utf8len.inc 201 | if (uint8(c) and 240) == 240 : utf8len.inc 202 | #echo utf8len + 1 203 | if utf8len > 0: 204 | for i in 1..utf8len: 205 | c = getch() 206 | str = str & c 207 | #echo str 208 | #char_cb( app, char_parser(str) ) 209 | result = char_parser(str) 210 | return result 211 | 212 | 213 | # ████████╗███████╗███████╗████████╗ 214 | # ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝ 215 | # ██║ █████╗ ███████╗ ██║ 216 | # ██║ ██╔══╝ ╚════██║ ██║ 217 | # ██║ ███████╗███████║ ██║ 218 | # ╚═╝ ╚══════╝╚══════╝ ╚═╝ 219 | 220 | 221 | 222 | when isMainModule: 223 | resetAttributes() 224 | echo "\e[?1002h\e[?1006h" 225 | #............ 226 | 227 | 228 | 229 | #............ 230 | echo "\e[?1006l\e[?1002l" 231 | resetAttributes() -------------------------------------------------------------------------------- /src/stui/ui_togglebutton.nim: -------------------------------------------------------------------------------- 1 | #import stui, terminal, colors, colors_extra, colors256, unicode, tables, os, locks 2 | include "controll.inc.nim" 3 | 4 | type ToggleButton* = ref object of Controll 5 | ## no border or padding style 6 | ## use paddingH, paddingV: int in 7 | ## proc newButton*(win:Window, label: string, paddingH: int = 0, paddingV:int = 0 ): Button = 8 | ## addEventListener("click", proc(source:Controll):void) for action 9 | 10 | #label*:string 11 | paddingH, paddingV: int 12 | val*, onValue*, offValue*: string 13 | 14 | 15 | proc `value`*(this:ToggleButton): string = 16 | ## return this.val as string - SQL etc likes this 17 | this.val 18 | 19 | proc `value2`*(this:ToggleButton): bool = 20 | ## return this.val as string - SQL etc likes this 21 | if this.val == this.onValue: 22 | return true 23 | else: 24 | return false 25 | 26 | proc `value=`*(this:ToggleButton, newVal:string) = 27 | if newVal == this.onValue: 28 | this.val = this.onValue 29 | else: 30 | this.val = this.offValue 31 | 32 | proc `value2=`*(this:ToggleButton, newVal:bool) = 33 | if newVal : 34 | this.val = this.onValue 35 | else: 36 | this.val = this.offValue 37 | 38 | proc toggle*(this:ToggleButton)= 39 | if this.val == this.onValue: 40 | this.val = this.offValue 41 | else: 42 | this.val = this.onValue 43 | 44 | 45 | 46 | proc setPaddingV*(this:ToggleButton, padding:int)= 47 | this.paddingV = padding 48 | this.heigth = 2 + (padding * 2) 49 | 50 | proc setPaddingH*(this:ToggleButton, padding:int)= 51 | this.paddingH = padding 52 | this.width = this.label.runeLen() + (this.paddingH * 2) 53 | 54 | proc draw*(this: ToggleButton, updateOnly: bool = false)= 55 | if this.visible: 56 | acquire(this.app.termlock) 57 | 58 | setColors(this.app, this.win.activeStyle[]) 59 | 60 | drawBorder(this.activeStyle.border, 61 | this.x1 + this.activeStyle.margin.left, 62 | this.y1 + this.activeStyle.margin.top, 63 | this.x2 - this.activeStyle.margin.right, 64 | this.y2 - this.activeStyle.margin.bottom 65 | ) 66 | 67 | if this.val == this.onValue: 68 | setColors(this.app, this.activeStyle[]) 69 | 70 | var cLine = this.topY 71 | for iP in 1..this.paddingV: 72 | terminal_extra.setCursorPos(this.leftX, cLine ) 73 | stdout.write(" " * this.width) 74 | cLine += 1 75 | 76 | if this.width_value != 0: # relative width; width_value == 0 by default 77 | terminal_extra.setCursorPos(this.leftX, cLine ) 78 | 79 | this.paddingH = (this.width - this.label.runeLen) div 2 80 | stdout.write(" " * this.paddingH) 81 | stdout.write(this.label) 82 | stdout.write(" " * (this.width - this.label.runeLen - this.paddingH))#(this.width - this.label.runeLen - (this.paddingH * 2)) ) 83 | else: # exactly: 84 | terminal_extra.setCursorPos(this.leftX, cLine ) 85 | stdout.write(" " * this.paddingH) 86 | stdout.write(this.label) 87 | stdout.write(" " * this.paddingH) 88 | 89 | cLine += 1 90 | for iP in 1..this.paddingV + 1: 91 | terminal_extra.setCursorPos(this.leftX, cLine ) 92 | stdout.write(" " * this.width) 93 | cLine += 1 94 | 95 | else: 96 | setColors(this.app, this.win.activeStyle[]) 97 | var cLine = this.topY 98 | for iP in 1..((this.paddingV * 2) + 1): 99 | terminal_extra.setCursorPos(this.leftX, cLine ) 100 | stdout.write(" " * this.width) 101 | cLine += 1 102 | 103 | cLine = this.bottomY 104 | terminal_extra.setCursorPos(this.leftX, cLine ) 105 | 106 | if this.width_value != 0: 107 | this.paddingH = (this.width - this.label.runeLen) div 2 108 | 109 | stdout.write(" " * this.paddingH) 110 | stdout.write(this.label) 111 | stdout.write(" " * (this.width - this.label.runeLen - this.paddingH))#(this.width - this.label.runeLen - (this.paddingH * 2)) ) 112 | 113 | #this.app.parkCursor() 114 | this.app.setCursorPos() 115 | 116 | release(this.app.termlock) 117 | 118 | 119 | 120 | proc drawit(this: Controll, updateOnly: bool = false) = 121 | draw(ToggleButton(this), updateOnly) 122 | 123 | 124 | 125 | 126 | proc focus(this: Controll)= 127 | this.prevStyle = this.activeStyle 128 | this.activeStyle = this.styles["input:focus"] 129 | 130 | 131 | proc blur(this: Controll)= 132 | if this.prevStyle != nil: this.activeStyle = this.prevStyle # prevstyle may not initialized 133 | 134 | proc cancel(this: Controll)=discard 135 | 136 | # made public to call if replaced - new method of event listener adding 137 | proc onClick*(this: Controll, event:KMEvent) = 138 | if not this.disabled: 139 | #this.focus(this) - it is already focused 140 | #drawit: 141 | ToggleButton(this).toggle() 142 | 143 | var c: char 144 | if event.evType != "CtrlKey": # mouseClick flush Release event 145 | while c != 'm': 146 | c = getch() 147 | 148 | this.blur(this) 149 | drawit(this) 150 | 151 | 152 | proc onDrag(this: Controll, event:KMEvent)=discard 153 | 154 | proc onDrop(this: Controll, event:KMEvent)=discard 155 | 156 | proc onKeyPress(this: Controll, event:KMEvent)= 157 | # todo: focusFWD on KeyLeft ? 158 | if not this.disabled: 159 | if event.evType == "CtrlKey": 160 | case event.ctrlKey: 161 | of 13: 162 | this.onClick(this, event) 163 | else: 164 | discard 165 | 166 | 167 | proc newToggleButton*(win:Window, label, onValue, offValue: string, paddingH: int = 0, paddingV:int = 0, toggled:bool = false): ToggleButton = 168 | result = new ToggleButton 169 | result.label=label 170 | result.heigth = 2 + (paddingV * 2) 171 | result.width = label.runeLen() + (paddingH * 2) # padding 172 | result.visible = false 173 | result.disabled = false 174 | result.paddingH = paddingH 175 | result.paddingV = paddingV 176 | 177 | result.onValue = onValue 178 | result.offValue = offValue 179 | if toggled: 180 | result.val = onValue 181 | else: 182 | result.val = offValue 183 | 184 | result.app = win.app 185 | result.win = win 186 | result.listeners = @[] 187 | 188 | result.styles = newStyleSheets() 189 | 190 | var styleNormal: StyleSheetRef = new StyleSheetRef 191 | styleNormal.deepcopy win.app.styles["input"] 192 | result.styles.add("input",styleNormal) 193 | result.activeStyle = result.styles["input"] 194 | 195 | var styleFocused: StyleSheetRef = new StyleSheetRef 196 | styleFocused.deepcopy win.app.styles["input:focus"] 197 | result.styles.add("input:focus",styleFocused) 198 | 199 | var styleDragged: StyleSheetRef = new StyleSheetRef 200 | styleDragged.deepCopy win.app.styles["input:drag"] 201 | result.styles.add("input:drag",styleDragged) 202 | 203 | var styleDisabled: StyleSheetRef = new StyleSheetRef 204 | styleDisabled.deepcopy win.app.styles["input:disabled"] 205 | result.styles.add("input:disabled", styleDisabled) 206 | 207 | result.drawit = drawit 208 | result.blur = blur 209 | result.focus = focus 210 | result.onClick = onClick 211 | result.onDrag = onDrag 212 | result.onDrop = onDrop 213 | result.cancel = cancel 214 | result.onKeypress = onKeyPress 215 | 216 | result.setBorder("solid") 217 | 218 | win.controlls.add(result) # typical finish line 219 | 220 | proc newToggleButton*(win:Window, label, onValue, offValue: string, width:string, paddingV:int = 0): ToggleButton = 221 | result = newToggleButton(win, label, onValue, offValue, 0, paddingV) 222 | discard width.parseInt(result.width_value) 223 | #discard heigth.parseInt(result.heigth_value) -------------------------------------------------------------------------------- /src/stui/appbase/appbasetypes.nim: -------------------------------------------------------------------------------- 1 | import os, locks, times, tables, json 2 | 3 | 4 | #------------------------------------------------------------------------------- 5 | # Base Types - to extend 6 | type 7 | EventRoot* = object of RootObj#dummy base 8 | 9 | TimedAction* = tuple[name:string,interval:float,action:proc():void,lastrun:float] # epochTime:float 10 | 11 | AppBase* = ref object of RootObj 12 | ## AppBase:------------------------------ 13 | ## extend with your needed fields: MyApp* = ref object of AppBase 14 | threadId*:int 15 | listeners*: seq[tuple[name:string, actions: seq[proc():void]]] 16 | timers*: seq[TimedAction] #seq[tuple[name:string,interval:float,action:proc()]] 17 | flags*: array[12, int] 18 | quit*: proc(errorcode: int = QuitSuccess) 19 | 20 | #------------------------------------------------------------------------------- 21 | 22 | #! Base Channels 23 | when defined mainChannelLog_enabled: 24 | type LogChannel* = ref Channel[string] 25 | var mainChannelLog = new LogChannel 26 | proc getLogChannel*(): LogChannel = mainChannelLog #! <-- getter ---- 27 | 28 | when defined mainChannelString_enabled: 29 | type McsChannel* = ref Channel[string] 30 | var mainChannelString = new McsChannel 31 | proc getMcsChannel*(): McsChannel = mainChannelString #! <-- getter ---- 32 | 33 | 34 | when defined mainChannelInt_enabled: 35 | type MciChannel = ref Channel[int] 36 | var mainChannelInt = new MciChannel 37 | proc getMciChannel*(): MciChannel = mainChannelInt #! <-- getter ------------ 38 | 39 | 40 | when defined mainChannelIntChecked_enabled: 41 | type 42 | McicChannel* = ref Channel[tuple[val:int,chan:ptr Channel[int]]] 43 | var mainChannelIntChecked = new McicChannel #Channel[tuple[val:int,chan:ptr Channel[int]]] 44 | proc getMcicChannel*(): McicChannel = mainChannelIntChecked #! <-- getter ----- 45 | 46 | when defined mainChannelIntTalkback_enabled: 47 | ## a talkback channel sends a pointer to a mutable variable, eg int, 48 | ## and changes it after operation -> change can be checked, variable can be 49 | ## destroyed 50 | ## atomicInc(intPtr[]) 51 | type McitChannel* = ref Channel[ptr int] 52 | var mainChannelIntTalkback = new McitChannel 53 | proc getMcitChannel*(): McitChannel = mainChannelIntTalkback #! <-- getter ----- 54 | 55 | proc waitFor*(chan: var McitChannel, msg: var int)= 56 | ## support proc: in a thread, sends ptr int then waits for talkback 57 | var temp = msg 58 | chan[].send(addr msg) 59 | while temp == msg: 60 | sleep(1) 61 | 62 | 63 | 64 | when defined mainChannelJsonChecked_enabled: 65 | ##!Thread InterComm, Json 66 | 67 | #! SUPPORT FUNCTIONS 68 | 69 | proc `%`*(p:pointer): JsonNode = 70 | ## store pointer as JsonNode, JInt 71 | when NimPatch < 9: %(cast[int](p)) 72 | else : %(cast[uint](p)) 73 | 74 | 75 | proc `<--`*[T](i:var uint, p:var T) = 76 | ## box operator: convert pointer to uint 77 | ## just to hide that ugly cast ;) 78 | i = cast[uint](p) 79 | 80 | proc `<--`*[T](p: var T, i: var uint)= 81 | ## box operator: convert uint to pointer 82 | ## just to hide that ugly cast ;) 83 | p = cast[T](i) 84 | 85 | proc `-->`*[T](p: var T, i: var uint) = 86 | ## box operator: convert pointer to uint 87 | ## just to hide that ugly cast ;) 88 | i = cast[uint](p) 89 | 90 | proc `-->`*[T](i:var uint, p:var T)= 91 | ## box operator: convert uint to pointer 92 | ## just to hide that ugly cast ;) 93 | p = cast[T](i) 94 | 95 | 96 | #!........................................ 97 | type 98 | # Main Channel [ msgtyp: int, msg: json ] 99 | #TODO ErrorCodeChannel* = Channel[ptr int] #REM: atomicInc(rchi[]) 100 | ChannelJson* = ref Channel[string] 101 | MainChannelJsonFeeds* = TableRef[int, ChannelJson] # TH id, Json 102 | InterCom* = TableRef[int, MainChannelJsonFeeds] 103 | 104 | var mainChannelJson = new ChannelJson 105 | proc getMcjChannel*():ChannelJson= mainChannelJson #! <-- getter ------------- 106 | 107 | var interCom = newTable[int, MainChannelJsonFeeds]() 108 | proc getInterCom*():InterCom = 109 | ## returns the intercom: 110 | ## a table of Json Channels tables 111 | ## MainChannelJsonFeeds* = TableRef[int, ChannelJson] # Thread id, Json 112 | ## InterCom* = TableRef[int, MainChannelJsonFeeds] # feedname, jsonfeeds 113 | interCom #! <-- getter ----------------------- 114 | 115 | 116 | 117 | 118 | 119 | proc broadcast*(feed: var MainChannelJsonFeeds, msg:string)= 120 | ## send msg to all feed subscribers 121 | for thId, listener in feed: 122 | open feed[thId][] 123 | feed[thId][].send(msg) 124 | 125 | proc sendTo*(feed: var MainChannelJsonFeeds, recip:int, msg:string)= 126 | ## send msg to recip, who is subscribed to feed 127 | for thId, listener in feed: 128 | if thId == recip: 129 | open feed[thId][] 130 | when defined debugInfo_enabled: debugEcho getThreadId(), " sendTo > ", recip 131 | feed[thId][].send(msg) 132 | when defined debugInfo_enabled: debugEcho getThreadId(), " peek > ", feed[thId][].peek() 133 | sleep(1) 134 | 135 | proc sendTo*(icom: var InterCom, recip:int, msg:string)= 136 | ## send msg to recip, finds recip in intercom 137 | var chan:ChannelJson # feed is immutable 138 | for feedname, feed in icom: 139 | for thId, listener in feed: 140 | if thId == recip: 141 | chan = feed[thId] # feed is immutable, cannot send 142 | open chan[] 143 | chan[].send(msg) 144 | 145 | 146 | 147 | 148 | 149 | proc getChannelJsonPtr*(n: JsonNode): ptr ChannelJson = 150 | ## Retrieves the int value as pointer of a `JInt JsonNode`. 151 | ## 152 | ## Returns ``default`` if ``n`` is not a ``JInt``, or if ``n`` is nil. 153 | if n.isNil or n.kind != JInt: return nil 154 | else: return cast[ptr ChannelJson](cast[uint](n.num)) 155 | 156 | proc getChannelJson*(n: JsonNode): ChannelJson = 157 | ## Retrieves the int value as pointer of a `JInt JsonNode`. 158 | ## 159 | ## Returns ``default`` if ``n`` is not a ``JInt``, or if ``n`` is nil. 160 | if n.isNil or n.kind != JInt: return nil 161 | else: return cast[ptr ChannelJson](cast[uint](n.num))[] 162 | 163 | 164 | 165 | 166 | 167 | #! Intercomm functions; ChannelJson; Main Channel [ msgtyp: int, msg: json ] 168 | proc addFeed*(icom: var InterCom, feedname:int)= 169 | ## add feed named feedname (int) to intercom and init feed 170 | when defined debugInfo_enabled: debugEcho "addFeed..........", feedname 171 | if not icom.hasKey(feedname): 172 | icom[feedname] = newTable[int, ChannelJson]() 173 | 174 | proc subscribe*(feed: var MainChannelJsonFeeds, thId: int, chan: var ChannelJson)= 175 | ## this proc is used in channel handler to add channel to feed 176 | feed[thId]= chan 177 | 178 | proc subscribe*(icom: var InterCom, feed, thId: int, chan: var ChannelJson):int= 179 | ## subscribe to user-defined feed 180 | ## return error code from reply: 181 | ## recommended reply from channel handler: 182 | ## interCom[feedname].sendTo(msgfrom, """{ "r": 0 }""") 183 | var testJson = newJObject() 184 | testJson.add("typ", newJInt(101)) 185 | testJson.add("from", newJInt(thId)) 186 | testJson.add("feed", newJInt(feed)) 187 | testJson.add("chanPtr", %(addr chan)) 188 | 189 | when defined debugInfo_enabled: debugEcho "subscribing to > ", testJson 190 | 191 | icom[0].sendTo(0, $testJson) 192 | sleep(1) 193 | 194 | while chan[].peek() == 0: 195 | when defined debugInfo_enabled: debugEcho chan[].peek() 196 | sleep(1) 197 | 198 | var replyJ = chan[].recv() 199 | when defined debugInfo_enabled: debugEcho thId, " > ", replyJ 200 | var r = parseJson(replyJ) 201 | 202 | return r["r"].getInt() 203 | 204 | proc subscribe*(icom: var InterCom, thId: int, chan: var ChannelJson):int= 205 | ## subscribe to main feed - 0 206 | ## send a message on intercom with proper json msg 207 | ## return error code from reply: 208 | ## recommended reply from channel handler: 209 | ## interCom[feedname].sendTo(msgfrom, """{ "r": 0 }""") 210 | return subscribe(icom, 0, thId, chan) 211 | 212 | 213 | 214 | ##[ 215 | var 216 | i:int=64 217 | p:ptr int 218 | ui: uint 219 | 220 | i=112 221 | p=nil 222 | ui<--p 223 | p = addr i 224 | p-->ui 225 | p=nil 226 | ui-->p 227 | p[]=453 228 | echo i ]## 229 | 230 | -------------------------------------------------------------------------------- /src/stui/ui_chooser.nim: -------------------------------------------------------------------------------- 1 | #import stui, terminal, colors, colors_extra, unicode 2 | include "controll.inc.nim" 3 | 4 | type 5 | Chooser* = ref object of Controll 6 | options*: OptionListRef # seq[ tuple[name, value:string, selected:bool] ] 7 | #win*:Window 8 | cursor:int 9 | prevActiveControll*: Controll 10 | multiSelect*:bool 11 | 12 | 13 | 14 | 15 | method draw*(this: Chooser, updateOnly:bool=false){.base.}= 16 | var 17 | firstSelected:int= 0 18 | drawCursor:int 19 | writeY:int 20 | 21 | proc writeOptionName(id:int)= 22 | stdout.write(this.options[id].name) 23 | if this.options[id].selected: # ☐☑☒☓⟦⟧⟰⦗⦘ 24 | stdout.write("☒") 25 | else: 26 | stdout.write("☐") 27 | 28 | #[ if this.cursor == -1: # cursor not yet set 29 | for i in 0..this.options[].high: 30 | if this.options[i].selected: 31 | firstSelected = i 32 | this.cursor = i 33 | else: 34 | firstSelected = this.cursor ]# 35 | 36 | # draw selected or first option with spec style, on win. middle:------------ 37 | firstSelected = this.cursor 38 | var winMiddleY = this.y1 + int((this.y2 - this.y1) / 2) - 1 #! 39 | var writeX = this.x1 + int((this.width - this.options[firstSelected].name.len) / 2) 40 | setColors(this.app, this.win.activeStyle[]) 41 | terminal_extra.setCursorPos(writeX - 4, winMiddleY) 42 | stdout.write("░▒▓█") 43 | terminal.setStyle(stdout, {terminal.styleReverse}) 44 | 45 | writeOptionName(firstSelected) 46 | 47 | terminal.resetAttributes() 48 | setColors(this.app, this.win.activeStyle[]) 49 | stdout.write("█▓▒░") 50 | 51 | 52 | # draw items below selected: ----------------------------------------------- 53 | drawCursor = firstSelected # back to beginner pos 54 | if firstSelected <= this.options[].high: # we are not on the end 55 | writeY = winMiddleY + 1 56 | drawCursor = drawCursor + 1 57 | while writeY <= this.y2 and drawCursor <= this.options[].high: # write till end or win botom 58 | writeX = this.x1 + int((this.width - this.options[drawCursor].name.len) / 2) 59 | terminal_extra.setCursorPos(writeX, writeY) 60 | stdout.write(this.options[drawCursor].name) 61 | if this.options[drawCursor].selected: # ☐☑☒☓⟦⟧⟰⦗⦘ 62 | stdout.write("☒") 63 | else: 64 | stdout.write("☐") 65 | drawCursor = drawCursor + 1 66 | writeY = writeY + 1 67 | 68 | 69 | # draw items above selected: ----------------------------------------------- 70 | drawCursor = firstSelected # back to beginner pos 71 | if firstSelected > 0: # if we need to print above winMiddleY 72 | writeY = winMiddleY - 1 73 | drawCursor = drawCursor - 1 74 | while writeY >= this.y1 and drawCursor >= 0: 75 | writeX = this.x1 + int((this.width - this.options[drawCursor].name.len) / 2) 76 | terminal_extra.setCursorPos(writeX, writeY) 77 | stdout.write(this.options[drawCursor].name) 78 | if this.options[drawCursor].selected: # ☐☑☒☓⟦⟧⟰⦗⦘ 79 | stdout.write("☒") 80 | else: 81 | stdout.write("☐") 82 | drawCursor = drawCursor - 1 83 | writeY = writeY - 1 84 | 85 | ### MANDATORY ### 86 | proc drawit(this: Controll, updateOnly:bool=false) = 87 | draw(Chooser(this),updateOnly) 88 | 89 | proc `heigth`*(this:Chooser):int= 90 | this.win.heigth - 1 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | proc cancel(this:Controll)= 100 | var chooser = Chooser(this) 101 | chooser.app.activeTile.windows.del(chooser.app.activeTile.windows.high) 102 | chooser.app.activeWindow.draw() 103 | if chooser.prevActiveControll.cancel != nil : chooser.prevActiveControll.cancel(chooser.prevActiveControll) 104 | chooser.app.activeControll = chooser.prevActiveControll 105 | 106 | proc commit(this:Controll)= 107 | var chooser = Chooser(this) 108 | chooser.app.activeTile.windows.del(chooser.app.activeTile.windows.high) 109 | chooser.app.activeWindow.draw() 110 | chooser.app.activeControll = chooser.prevActiveControll 111 | chooser.app.activeControll.trigger("change") 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | proc onKeypress(this:Controll, event:KMEvent)= 123 | var chooser = Chooser(this) 124 | if event.evType == "FnKey": #,"CtrlKey", "Char": 125 | case event.key: 126 | of KeyUp: 127 | if chooser.cursor > 0 : 128 | chooser.cursor = chooser.cursor - 1 129 | chooser.app.activeWindow.draw() 130 | 131 | of KeyDown: 132 | if chooser.cursor < chooser.options[].high : 133 | chooser.cursor = chooser.cursor + 1 134 | chooser.app.activeWindow.draw() 135 | 136 | else: discard 137 | 138 | if event.evType == "CtrlKey": 139 | case event.ctrlKey: 140 | of 13: # ENTER, ctrl+M 141 | #chooser.options[chooser.cursor].selected = true 142 | commit(this) 143 | else: discard 144 | 145 | if event.evType == "Char": 146 | case event.key: 147 | of " ": 148 | if chooser.multiSelect == false or chooser.cursor == 0: 149 | for i in 0..chooser.options[].high: 150 | chooser.options[i].selected = false 151 | 152 | #[ if chooser.cursor != 0: 153 | chooser.options[0].selected = false ]# 154 | chooser.options[chooser.cursor].selected = not chooser.options[chooser.cursor].selected 155 | 156 | var b:bool=true 157 | for i in 1..chooser.options[].high: 158 | if chooser.options[i].selected: b = false 159 | chooser.options[0].selected = b 160 | 161 | chooser.app.activeWindow.draw() 162 | else: discard 163 | 164 | 165 | 166 | proc onClick(this:Controll, event:KMEvent)= 167 | var chooser = Chooser(this) 168 | 169 | #if event.btn > 0: cancel(this) 170 | 171 | case event.btn: 172 | of 0: 173 | 174 | var winMiddleY = this.y1 + int((this.y2 - this.y1) / 2) - 1 #! 175 | 176 | if event.y > winMiddleY: 177 | if chooser.cursor + (event.y - winMiddleY) <= chooser.options[].high: 178 | chooser.cursor = chooser.cursor + (event.y - winMiddleY) 179 | 180 | if chooser.multiSelect == false or chooser.cursor == 0: 181 | for i in 0..chooser.options[].high: 182 | chooser.options[i].selected = false 183 | 184 | #[ if chooser.cursor != 0: 185 | chooser.options[0].selected = false ]# 186 | chooser.options[chooser.cursor].selected = not chooser.options[chooser.cursor].selected 187 | 188 | var b:bool=true 189 | for i in 1..chooser.options[].high: 190 | if chooser.options[i].selected: b = false 191 | chooser.options[0].selected = b 192 | 193 | chooser.app.activeWindow.draw() 194 | else: 195 | cancel(this) 196 | 197 | 198 | elif event.y < winMiddleY: 199 | if chooser.cursor - (winMiddleY - event.y) >= 0: 200 | chooser.cursor = chooser.cursor - (winMiddleY - event.y) 201 | 202 | if chooser.multiSelect == false or chooser.cursor == 0: 203 | for i in 0..chooser.options[].high: 204 | chooser.options[i].selected = false 205 | 206 | #[ if chooser.cursor != 0: 207 | chooser.options[0].selected = false ]# 208 | 209 | chooser.options[chooser.cursor].selected = not chooser.options[chooser.cursor].selected 210 | 211 | var b:bool=true 212 | for i in 1..chooser.options[].high: 213 | if chooser.options[i].selected: b = false 214 | chooser.options[0].selected = b 215 | 216 | chooser.app.activeWindow.draw() 217 | else: 218 | cancel(this) 219 | 220 | elif event.y == winMiddleY: 221 | if chooser.multiSelect == false or chooser.cursor == 0: 222 | for i in 0..chooser.options[].high: 223 | chooser.options[i].selected = false 224 | 225 | if chooser.cursor != 0: 226 | chooser.options[0].selected = false 227 | 228 | chooser.options[chooser.cursor].selected = true 229 | chooser.app.activeWindow.draw() 230 | 231 | else: 232 | #cancel(this) 233 | commit(this) 234 | 235 | 236 | proc newChooser*(win:Window, options:OptionListRef, multiSelect:bool=false):Chooser= 237 | result = new Chooser 238 | result.disabled = false 239 | result.win = win 240 | result.app = win.app 241 | result.options = options 242 | result.multiSelect = multiSelect 243 | result.cursor = 0 244 | result.onKeypress = onKeypress 245 | result.cancel = cancel 246 | result.onClick = onClick 247 | result.drawit = drawit 248 | 249 | #win.controlls.add(result) -------------------------------------------------------------------------------- /src/stui/ui_fineprogressbar.nim: -------------------------------------------------------------------------------- 1 | #import stui, terminal, colors, colors_extra, unicode, tables, parseutils, locks 2 | include "controll.inc.nim" 3 | 4 | 5 | type FineProgressBar* = ref object of Controll 6 | #label*:string 7 | val*:int # 0..100 (%) 8 | preval*:int # undo 9 | 10 | levels*: tuple[normal, warning:int] 11 | levelStyles*: tuple[normal, warning, error:StyleSheetRef] 12 | 13 | #size*:int # of input 14 | 15 | 16 | 17 | 18 | proc draw*(this: FineProgressBar, updateOnly: bool = false) = 19 | if this.visible: 20 | acquire(this.app.termlock) 21 | 22 | #stdout.write "\e[?25l" 23 | hideCursor() 24 | 25 | if not updateOnly: 26 | setColors(this.app, this.win.activeStyle[]) 27 | terminal_extra.setCursorPos(this.x1 + this.activeStyle.margin.left, 28 | this.y1 + this.activeStyle.margin.top) 29 | stdout.write this.label 30 | 31 | 32 | # draw border 33 | drawBorder(this.activeStyle.border, 34 | this.x1 + this.activeStyle.margin.left, 35 | this.y1 + this.activeStyle.margin.top + 1 #[label]#, 36 | this.x2 - this.activeStyle.margin.right, 37 | this.y2 - this.activeStyle.margin.bottom 38 | ) 39 | #... 40 | 41 | # echo input area 42 | 43 | if this.val <= this.levels.normal: 44 | setColors(this.app, this.activeStyle[]) 45 | elif this.val <= this.levels.warning: 46 | setColors(this.app, this.styles["input:warning"]) 47 | else: 48 | setColors(this.app, this.styles["input:error"]) 49 | 50 | 51 | 52 | terminal_extra.setCursorPos(this.leftX(), 53 | this.bottomY()) 54 | # █ ▓ ░ 55 | #U+2588 █ e2 96 88 56 | #U+2589 ▉ e2 96 89 57 | #U+258a ▊ e2 96 8a 58 | #U+258b ▋ e2 96 8b 59 | 60 | #U+258c ▌ e2 96 8c 61 | #U+258d ▍ e2 96 8d 62 | #U+258e ▎ e2 96 8e 63 | #U+258f ▏ 64 | let 65 | onePc: float = (this.width / 100) 66 | hBarCharset = ["","▏","▎","▍","▌", "▋","▊","▉","█"] 67 | var 68 | size = int( onePc * float(this.val) ) 69 | rem = int( ((onePc * float(this.val)) - size.float) / (1/8) ) 70 | 71 | #! about '\n': 72 | #! Stream Type Behavior 73 | #! stdin input line-buffered 74 | #! stdout (TTY) output line-buffered 75 | #! stdout (not a TTY) output fully-buffered 76 | #! stderr output unbuffered 77 | 78 | if size > 0 : 79 | #stdout.write $size 80 | if rem > 0: 81 | stdout.write ("▓" * size) & hBarCharset[rem] 82 | stdout.write " " * (this.width - size - 1) 83 | else: 84 | stdout.write ("▓" * size) 85 | stdout.write " " * (this.width - size) 86 | 87 | stdout.write '\n' #! 88 | else: 89 | #stdout.write $size 90 | stdout.write " " * this.width 91 | stdout.write '\n' #! 92 | 93 | #this.app.parkCursor() 94 | this.app.setCursorPos() 95 | #stdout.write "\e[?25h" 96 | showCursor() 97 | release(this.app.termlock) 98 | 99 | 100 | ### MANDATORY ### 101 | proc drawit(this: Controll, updateOnly: bool = false) = 102 | draw(FineProgressBar(this), updateOnly) 103 | 104 | 105 | 106 | 107 | 108 | proc `value=`*(this: FineProgressBar, str:string) = 109 | discard parseInt(str, this.val) 110 | if this.val > 100: this.val = 100 111 | if this.visible: this.draw() 112 | trigger(this, "change") 113 | 114 | proc `value`*(this: FineProgressBar): string = $this.val 115 | 116 | # Textbox value and value2 same type as val : string 117 | proc `value2=`*(this: FineProgressBar, val:int) = 118 | this.val = val 119 | if this.val > 100: this.val = 100 120 | if this.visible: this.draw() 121 | trigger(this, "change") 122 | 123 | proc `value2`*(this: FineProgressBar): int = this.val 124 | 125 | 126 | 127 | 128 | 129 | proc focus(this: Controll)= 130 | this.prevStyle = this.activeStyle 131 | this.activeStyle = this.styles["input:focus"] 132 | 133 | 134 | 135 | proc blur(this: Controll)= 136 | if this.prevStyle != nil: # prevstyle may not initialized 137 | this.activeStyle = this.prevStyle 138 | 139 | 140 | 141 | 142 | proc cancel(this: Controll)= 143 | FineProgressBar(this).val = 0 144 | FineProgressBar(this).blur(this) 145 | FineProgressBar(this).draw() 146 | 147 | 148 | proc onClick(this: Controll, event: KMEvent):void= 149 | if not this.disabled: 150 | trigger(this, "click") 151 | FineProgressBar(this).draw() 152 | #[ 153 | proc onKeyPress(this: Controll, event: KMEvent)= 154 | discard 155 | 156 | 157 | 158 | 159 | 160 | proc onDrop(this: Controll, event: KMEvent):void= 161 | discard 162 | 163 | 164 | proc onDrag(this: Controll, event: KMEvent)= 165 | discard 166 | ]# 167 | 168 | 169 | 170 | 171 | 172 | 173 | ######## # # ###### # # 174 | ######## ## # # # # 175 | ######## # # # ##### # # 176 | ######## # # # # # ## # 177 | ######## # ## # ## ## 178 | ######## # # ###### # # 179 | 180 | proc newFineProgressBar*(win:Window, label: string, width:int=20): FineProgressBar = 181 | result = new FineProgressBar 182 | result.label=label 183 | 184 | result.visible = false 185 | result.disabled = false 186 | 187 | result.width = width 188 | result.heigth = 2 189 | result.styles = newTable[string, StyleSheetRef](8) 190 | 191 | var styleNormal: StyleSheetRef = new StyleSheetRef 192 | styleNormal.deepcopy win.app.styles["input:inverse"] 193 | result.styles.add("input",styleNormal) 194 | #[ styleNormal.deepcopy win.app.styles["input"] 195 | result.styles.add("input",styleNormal) 196 | styleNormal.fgColor = styleNormal.bgColor 197 | styleNormal.bgColor = win.app.styles["window"].bgColor ]# 198 | 199 | var styleFocused: StyleSheetRef = new StyleSheetRef 200 | styleFocused.deepcopy win.app.styles["input:focus"] 201 | result.styles.add("input:focus",styleFocused) 202 | 203 | var styleDragged: StyleSheetRef = new StyleSheetRef 204 | styleDragged.deepCopy win.app.styles["input:drag"] 205 | result.styles.add("input:drag",styleDragged) 206 | 207 | var styleDisabled: StyleSheetRef = new StyleSheetRef 208 | styleDisabled.deepcopy win.app.styles["input:disabled"] 209 | result.styles.add("input:disabled", styleDisabled) 210 | 211 | result.activeStyle = result.styles["input"] 212 | 213 | var styleWarning: StyleSheetRef = new StyleSheetRef 214 | styleWarning.deepcopy win.app.styles["input:inverse"] 215 | styleWarning.fgColor[0] = fgYellow.int 216 | styleWarning.fgColor[1] = fgYellow.int 217 | styleWarning.fgColor[2] = parseColor("gold",2) 218 | styleWarning.fgColor[3] = parseColor("gold",3) 219 | 220 | #[ styleWarning.bgColor[0] = bgRed.int 221 | styleWarning.bgColor[1] = bgRed.int 222 | styleWarning.bgColor[2] = parseColor("maroon",2) 223 | styleWarning.bgColor[3] = parseColor("maroon",3) ]# 224 | result.styles.add("input:warning",styleWarning) 225 | 226 | var styleError: StyleSheetRef = new StyleSheetRef 227 | styleError.deepcopy win.app.styles["input:inverse"] 228 | styleError.fgColor[0] = fgRed.int 229 | styleError.fgColor[1] = fgRed.int 230 | styleError.fgColor[2] = parseColor("red",2) 231 | styleError.fgColor[3] = parseColor("red",3) 232 | 233 | styleError.bgColor[0] = bgYellow.int 234 | styleError.bgColor[1] = bgYellow.int 235 | styleError.bgColor[2] = parseColor("maroon",2) 236 | styleError.bgColor[3] = parseColor("maroon",3) 237 | 238 | styleError.setTextStyle("styleBlink") 239 | result.styles.add("input:error",styleError) 240 | 241 | result.levels.normal = 100 242 | result.levels.warning = 100 243 | 244 | result.drawit = drawit 245 | result.blur = blur 246 | result.focus = focus 247 | result.cancel = cancel 248 | #[ result.onClick = onClick 249 | result.onDrag = onDrag 250 | result.onDrop = onDrop 251 | result.onKeypress = onKeyPress ]# 252 | 253 | result.app = win.app 254 | result.win = win 255 | result.listeners = @[] 256 | 257 | win.controlls.add(result) 258 | 259 | 260 | proc newFineProgressBar*(win:Window, label: string, width:int=20, 261 | levels: tuple[normal, warning:int]): FineProgressBar = 262 | 263 | result = newFineProgressBar(win, label, width) 264 | result.levels = levels 265 | 266 | 267 | proc newFineProgressBar*(win:Window, label: string, width:int=20, 268 | levels: tuple[normal, warning:int], 269 | levelStyles: tuple[warning, error:StyleSheetRef]): FineProgressBar = 270 | 271 | result = newFineProgressBar(win, label, width) 272 | result.levels = levels 273 | 274 | result.styles["input:warning"].copyColorsFrom(levelStyles.warning) 275 | result.styles["input:error"].copyColorsFrom(levelStyles.error) 276 | 277 | 278 | # relativeW::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 279 | proc newFineProgressBar*(win:Window, label: string, width:string): FineProgressBar = 280 | result = newFineProgressBar(win, label, width = 0) 281 | discard width.parseInt(result.width_value) 282 | 283 | 284 | proc newFineProgressBar*(win:Window, label: string, width:string, 285 | levels: tuple[normal, warning:int]): FineProgressBar = 286 | 287 | result = newFineProgressBar(win, label, width = 0, levels) 288 | discard width.parseInt(result.width_value) 289 | 290 | 291 | proc newFineProgressBar*(win:Window, label: string, width:string, 292 | levels: tuple[normal, warning:int], 293 | levelStyles: tuple[warning, error:StyleSheetRef]): FineProgressBar = 294 | 295 | result = newFineProgressBar(win, label, width = 0) 296 | result.levels = levels 297 | 298 | result.styles["input:warning"].copyColorsFrom(levelStyles.warning) 299 | result.styles["input:error"].copyColorsFrom(levelStyles.error) 300 | 301 | discard width.parseInt(result.width_value) -------------------------------------------------------------------------------- /src/stui/ui_stringlistbox.nim: -------------------------------------------------------------------------------- 1 | include "controll.inc.nim" 2 | 3 | type 4 | StringListBox_Options* = seq[ tuple[name:string, action:proc():void] ] 5 | StringListBox* = ref object of Controll 6 | ## it is a "listview". 7 | ## the items are strings. 8 | ## onclick triggers the items.action:proc():void 9 | ## no events - as it has actions 10 | 11 | offset*:int # num-lines scrolled down 12 | options*: StringListBox_Options #seq[ tuple[name:string, action:proc():void] ] 13 | #win*:Window 14 | cursor:int 15 | 16 | prevActiveControll*: Controll # it is here for more fun - but not yet demoed... 17 | 18 | 19 | proc writeFromOffset(this: StringListBox)= 20 | ## draw content from scroll offset (linenum) 21 | 22 | var 23 | currentLine, currentY: int 24 | 25 | currentLine = this.offset 26 | currentY = this.topY() + 1 # +1 label 27 | 28 | 29 | while currentY <= this.bottomY() and currentLine <= this.options.high: 30 | # todo: style: even,odd,highlight 31 | if this.cursor == currentLine and this.app.activeControll == this: #! todo 32 | setColors(this.app, this.styles["input:focus"]) 33 | this.app.cursorPos.y = currentY #! patch for scroll issues 34 | elif currentLine mod 2 == 0: 35 | setColors(this.app, this.styles["input:even"]) 36 | else: 37 | setColors(this.app, this.styles["input:odd"]) 38 | 39 | terminal_extra.setCursorPos(this.leftX, currentY ) 40 | 41 | if this.options[currentLine].name.runeLen() == this.width: 42 | stdout.write(this.options[currentLine].name) 43 | elif this.options[currentLine].name.runeLen() <= this.width: 44 | stdout.write(this.options[currentLine].name) 45 | stdout.write " " * (this.width - this.options[currentLine].name.runeLen()) 46 | else: 47 | stdout.write this.options[currentLine].name.runeSubStr(0,this.width) 48 | 49 | currentLine += 1 50 | currentY += 1 51 | 52 | 53 | 54 | method draw*(this: StringListBox, updateOnly:bool=false){.base.}= 55 | if this.visible: 56 | acquire(this.app.termlock) 57 | 58 | if not updateOnly : 59 | setColors(this.app, this.win.activeStyle[]) 60 | terminal_extra.setCursorPos(this.x1 + this.activeStyle.margin.left, 61 | this.y1 + this.activeStyle.margin.top) 62 | stdout.write this.label 63 | 64 | 65 | # draw border 66 | drawBorder(this.activeStyle.border, 67 | this.x1 + this.activeStyle.margin.left, 68 | this.y1 + this.activeStyle.margin.top + 1, 69 | this.x2 - this.activeStyle.margin.right, 70 | this.y2 - this.activeStyle.margin.bottom 71 | ) 72 | #... 73 | setColors(this.app, this.styles["input"]) 74 | drawRect(this.leftX, this.topY + 1, this.rightX, this.bottomY) 75 | this.writeFromOffset() 76 | #this.app.setCursorPos() 77 | release(this.app.termlock) 78 | 79 | ### MANDATORY ### 80 | proc drawit(this: Controll, updateOnly:bool=false) = 81 | draw(StringListBox(this),updateOnly) 82 | 83 | 84 | 85 | 86 | proc focus(this: Controll)= 87 | this.prevStyle = this.activeStyle 88 | this.activeStyle = this.styles["input:focus"] 89 | 90 | this.app.cursorPos.y = this.topY + 1 91 | this.app.cursorPos.x = this.leftX 92 | this.app.setCursorPos() 93 | 94 | hideCursor() 95 | 96 | 97 | 98 | 99 | 100 | proc blur(this: Controll)= 101 | if this.prevStyle != nil: # prevstyle may not initialized 102 | this.activeStyle = this.prevStyle 103 | 104 | this.app.activeControll = this.win #! patch for draw if this == activecontroll 105 | 106 | StringListBox(this).draw() 107 | this.app.parkCursor() 108 | 109 | if StringListBox(this).prevActiveControll != nil: 110 | this.app.activate(StringListBox(this).prevActiveControll) 111 | 112 | 113 | 114 | 115 | proc cancel(this:Controll)= 116 | var slistbox = StringListBox(this) 117 | slistbox.blur(this) 118 | slistbox.draw() 119 | 120 | 121 | 122 | 123 | proc onClick(this:Controll, event:KMEvent)= 124 | StringListBox(this).draw() 125 | 126 | if not this.disabled: 127 | var slistbox = StringListBox(this) 128 | 129 | case event.btn: 130 | of 0: 131 | if clickedInside(this,event): 132 | if ((event.y - (slistbox.topY + 1)) + slistbox.offset) <= slistbox.options.high: # if not clicked on empty space 133 | let selected = (event.y - (slistbox.topY + 1)) + slistbox.offset 134 | 135 | # visuals: 136 | slistbox.cursor = selected 137 | slistbox.draw() 138 | # action 139 | if slistbox.options[selected].action != nil: 140 | slistbox.options[selected].action() 141 | else: 142 | #slistbox.draw() 143 | discard 144 | 145 | 146 | proc onKeyUp(this: StringListBox) = 147 | #echo "keyuppppp" 148 | if this.cursor > 0: 149 | this.cursor -= 1 150 | if this.app.cursorPos.y == this.topY + 1: 151 | this.offset -= 1 152 | else: 153 | this.app.cursorPos.y -= 1 154 | 155 | #this.app.setCursorPos() 156 | this.draw(true) 157 | 158 | proc onKeyDown(this: StringListBox) = 159 | #echo "keydooooown" 160 | if this.cursor < this.options.high: 161 | this.cursor += 1 162 | if this.app.cursorPos.y == this.bottomY: 163 | this.offset += 1 164 | else: 165 | this.app.cursorPos.y += 1 166 | 167 | #this.app.setCursorPos() 168 | this.draw() 169 | 170 | proc onPgUp(this: StringListBox) = 171 | if this.offset > this.heigth - 1: 172 | this.offset -= (this.heigth - 1) 173 | this.cursor -= (this.heigth - 1) 174 | else: 175 | this.offset = 0 176 | this.cursor = 0 177 | this.draw(true) 178 | 179 | 180 | proc onPgDown(this: StringListBox) = 181 | if this.options.len > this.heigth: 182 | if this.offset + (this.heigth - 1) < this.options.len - (this.heigth - 1): 183 | # jump a page down 184 | this.offset += (this.heigth - 1) 185 | this.cursor = this.offset 186 | else: 187 | if this.offset == this.options.len - (this.heigth - 1): 188 | this.cursor = this.options.high 189 | this.app.cursorPos.y = this.bottomY 190 | else: 191 | this.offset = this.options.len - (this.heigth - 1) 192 | this.cursor = this.offset 193 | this.draw(true) 194 | 195 | 196 | proc onHome(this: StringListBox)= 197 | this.offset = 0 198 | this.cursor = 0 199 | this.draw(true) 200 | 201 | proc onEnd(this: StringListBox)= 202 | this.offset = this.options.len - (this.heigth - 1) 203 | this.cursor = this.options.high 204 | this.draw(true) 205 | 206 | 207 | proc onKeypress(this:Controll, event:KMEvent)= 208 | if not this.disabled: 209 | let slistbox = StringListBox(this) 210 | 211 | if event.evType == "FnKey": #.....FnKey.....FnKey.....FnKey.....FnKey... 212 | case event.key: 213 | of KeyDown: 214 | onKeyDown(slistbox) 215 | 216 | of KeyUP: 217 | onKeyUp(StringListBox(this)) 218 | 219 | of KeyPgDown: onPgDown(StringListBox(this)) 220 | of KeyPgUp: onPgUp(StringListBox(this)) 221 | 222 | of KeyHome: onHome(StringListBox(this)) 223 | of KeyEnd: onEnd(StringListBox(this)) 224 | 225 | else: discard 226 | 227 | elif event.evType == "CtrlKey": 228 | case event.ctrlKey: 229 | of 13: # ENTER, ctrl+M 230 | if slistbox.options[slistbox.cursor].action != nil: 231 | slistbox.options[slistbox.cursor].action() 232 | 233 | else: discard 234 | 235 | 236 | proc onScroll(this:Controll, event:KMEvent)= 237 | case event.evType: 238 | of "ScrollUp": StringListBox(this).onPgUp() 239 | of "ScrollDown": StringListBox(this).onPgDown() 240 | else: discard 241 | 242 | 243 | 244 | 245 | ######## # # ###### # # 246 | ######## ## # # # # 247 | ######## # # # ##### # # 248 | ######## # # # # # ## # 249 | ######## # ## # ## ## 250 | ######## # # ###### # # 251 | 252 | proc newStringListBox*(win:Window, label: string, width:int=20, heigth:int=20): StringListBox = 253 | result = new StringListBox 254 | result.disabled = false 255 | 256 | result.label = label 257 | 258 | result.width = width 259 | result.heigth = heigth 260 | 261 | result.options = @[] 262 | 263 | result.styles = newStyleSheets() 264 | 265 | var styleNormal: StyleSheetRef = new StyleSheetRef 266 | styleNormal.deepcopy win.app.styles["input"] 267 | styleNormal.border="none" 268 | #styleNormal.setTextStyle("styleUnderline") #! disabled because border draw 269 | result.styles.add("input",styleNormal) 270 | result.activeStyle = result.styles["input"] 271 | 272 | var styleEven: StyleSheetRef = new StyleSheetRef 273 | styleEven.deepcopy win.app.styles["input:even"] 274 | #styleEven.setTextStyle("styleUnderline") #! 275 | result.styles.add("input:even",styleEven) 276 | 277 | var styleOdd: StyleSheetRef = new StyleSheetRef 278 | styleOdd.deepcopy win.app.styles["input:odd"] 279 | #styleOdd.setTextStyle("styleUnderline") #! 280 | result.styles.add("input:odd",styleOdd) 281 | 282 | 283 | var styleFocused: StyleSheetRef = new StyleSheetRef 284 | styleFocused.deepcopy win.app.styles["input:focus"] 285 | #styleFocused.setTextStyle("styleUnderline") #! disabled because border draw 286 | result.styles.add("input:focus",styleFocused) 287 | 288 | var styleDragged: StyleSheetRef = new StyleSheetRef 289 | styleDragged.deepCopy win.app.styles["input:drag"] 290 | styleDragged.setTextStyle("styleUnderline") #! 291 | result.styles.add("input:drag",styleDragged) 292 | 293 | #??? 294 | var styleDisabled: StyleSheetRef = new StyleSheetRef 295 | styleDisabled.deepcopy win.app.styles["input:disabled"] 296 | styleDisabled.setTextStyle("styleUnderline") #! 297 | result.styles.add("input:disabled", styleDisabled) 298 | 299 | result.drawit = drawit 300 | result.blur = blur 301 | result.focus = focus 302 | result.onClick = onClick 303 | #result.onDrag = onDrag 304 | #result.onDrop = onDrop 305 | result.cancel = cancel 306 | result.onKeypress = onKeyPress 307 | result.onScroll = onScroll 308 | 309 | result.app = win.app 310 | result.win = win 311 | result.listeners = @[] 312 | 313 | win.controlls.add(result) 314 | 315 | 316 | proc newStringListBox*(win:Window, label: string, width:string, heigth:string): StringListBox = 317 | result = newStringListBox(win, label) 318 | discard width.parseInt(result.width_value) 319 | discard heigth.parseInt(result.heigth_value) -------------------------------------------------------------------------------- /src/stui/ui_selectbox.nim: -------------------------------------------------------------------------------- 1 | include "controll.inc.nim" 2 | #import stui, terminal, colors, colors_extra, unicode, tables, locks 3 | #import strutils, parseutils 4 | import ui_chooser 5 | 6 | type 7 | 8 | SelectBox* = ref object of Controll 9 | ## uses Chooser controll to select value(s) 10 | ## triggers "change" on blur or chooser change 11 | #label*:string 12 | val*:string # for store 13 | text*:string # for display 14 | options*: OptionListRef # seq[ tuple[name, value:string, selected:bool] ] 15 | preval*: OptionList # undo 16 | 17 | multiSelect:bool 18 | 19 | #width*:int # of input 20 | 21 | offset_h*:int 22 | #cursor_pos*:int 23 | 24 | 25 | chooser*: Chooser 26 | 27 | #[ proc `value=`*(this: SelectBox, str:string) = 28 | this.val = str 29 | if this.visible: this.draw() ]# 30 | 31 | proc draw*(this: SelectBox, updateOnly: bool = false) #FWD 32 | 33 | 34 | 35 | 36 | proc clear*(this:SelectBox)= 37 | for i in 1..this.options[].high: this.options[i].selected = false 38 | this.options[0].selected = true 39 | 40 | 41 | proc `value`*(this:SelectBox): string = 42 | ## returns string with 1 option or comma separated string of values 43 | result = "" 44 | for i in 1..this.options[].high: 45 | if this.options[i].selected: 46 | if result.len == 0: 47 | result.add(this.options[i].value) 48 | else: 49 | result.add("," & this.options[i].value) 50 | 51 | proc selectBoxOnChange*(this: Controll) #FWD 52 | 53 | proc `value=`*(this:SelectBox, val:string) = 54 | this.clear() 55 | for word in split(val, ','): 56 | for i in 1..this.options[].high: 57 | if this.options[i].value == word: 58 | this.options[i].selected = true 59 | selectBoxOnChange(Controll(this)) 60 | this.draw(true) 61 | 62 | proc `value2`*(this:SelectBox): string = `value`(this) 63 | 64 | 65 | 66 | proc `values`*(this:SelectBox): seq[string] = 67 | ## returns a seq of selected option VALUES 68 | result = @[] 69 | for i in 1..this.options[].high: 70 | if this.options[i].selected: result.add(this.options[i].value) 71 | 72 | proc `values=`*(this:SelectBox, selected: seq[string]) = 73 | this.clear() 74 | for c in 0..selected.high: 75 | for i in 1..this.options[].high: 76 | if this.options[i].value == selected[c]: this.options[i].selected = true 77 | selectBoxOnChange(Controll(this)) 78 | this.draw(true) 79 | 80 | 81 | ################ these functions working with NAME prop!!! ################ 82 | proc `names`*(this:SelectBox): string = 83 | ## returns selected opts name or comma sep string of selected names 84 | result = "" 85 | for i in 1..this.options[].high: 86 | if this.options[i].selected: 87 | if result.len == 0: 88 | result.add(this.options[i].name) 89 | else: 90 | result.add("," & this.options[i].name) 91 | 92 | proc `names=`*(this:SelectBox, val:string) = 93 | this.clear() 94 | for word in split(val, ','): 95 | for i in 1..this.options[].high: 96 | if this.options[i].name == word: this.options[i].selected = true 97 | selectBoxOnChange(Controll(this)) 98 | this.draw(true) 99 | 100 | proc `names2`*(this:SelectBox): seq[string] = 101 | ## returns a seq of selected option NAMEs 102 | result = @[] 103 | for i in 1..this.options[].high: 104 | if this.options[i].selected: result.add(this.options[i].name) 105 | 106 | proc `names2=`*(this:SelectBox, selected: seq[string]) = 107 | this.clear() 108 | for c in 0..selected.high: 109 | for i in 1..this.options[].high: 110 | if this.options[i].name == selected[c]: this.options[i].selected = true 111 | selectBoxOnChange(Controll(this)) 112 | this.draw(true) 113 | 114 | 115 | 116 | proc deselectAll*(this:SelectBox)= 117 | for i in 0..this.options[].high: 118 | this.options[i].selected = false 119 | 120 | #---------------------------------- 121 | 122 | 123 | proc draw*(this: SelectBox, updateOnly: bool = false) = 124 | #echo "TB" 125 | if this.visible: 126 | #echo "TV" 127 | acquire(this.app.termlock) 128 | 129 | if not updateOnly: 130 | setColors(this.app, this.win.activeStyle[]) 131 | terminal_extra.setCursorPos(this.x1 + this.activeStyle.margin.left, 132 | this.y1 + this.activeStyle.margin.top) 133 | stdout.write this.label 134 | 135 | # draw border 136 | drawBorder(this.activeStyle.border, 137 | this.x1 + this.activeStyle.margin.left, 138 | this.y1 + this.activeStyle.margin.top + 1 #[ +1 label]#, 139 | this.x2 - this.activeStyle.margin.right, 140 | this.y2 - this.activeStyle.margin.bottom 141 | ) 142 | #... 143 | 144 | setColors(this.app, this.activeStyle[]) 145 | terminal_extra.setCursorPos(this.leftX(), 146 | this.bottomY()) 147 | if this.text.runeLen > 0 : 148 | if this.text.runeLen < (this.width - 1): 149 | stdout.write this.text 150 | stdout.write " " * ((this.width - 1) - this.text.runeLen) & "▼" 151 | else: 152 | if this.offset_h + (this.width - 1) < this.text.runeLen: 153 | stdout.write this.text.runeSubStr(this.offset_h, (this.width - 1) - 1) & "…▼" 154 | else: 155 | var used = (this.text.runeLen - this.offset_h - 1) 156 | stdout.write this.text.runeSubStr(this.offset_h, used) 157 | stdout.write " " * ((this.width - 1) - used) & "▼" 158 | else: 159 | stdout.write " " * (this.width - 1) & "▼" 160 | 161 | 162 | #setColors(this.app, this.app.activeStyle[]) 163 | #this.app.parkCursor() 164 | this.app.setCursorPos() 165 | release(this.app.termlock) 166 | 167 | ### MANDATORY ### 168 | proc drawit(this: Controll, updateOnly: bool = false) = 169 | draw(SelectBox(this), updateOnly) 170 | 171 | 172 | 173 | 174 | proc focus(this: Controll)= 175 | this.prevStyle = this.activeStyle 176 | this.activeStyle = this.styles["input:focus"] 177 | this.drawit(this,false) 178 | 179 | proc blur(this: Controll)= 180 | if this.prevStyle != nil: this.activeStyle = this.prevStyle # prevstyle may not initialized 181 | trigger(this, "change") 182 | 183 | proc cancel(this: Controll)= 184 | SelectBox(this).options[].deepCopy SelectBox(this).preval 185 | 186 | 187 | 188 | 189 | 190 | 191 | proc selectBoxOnClick(this:Controll, event:KMEvent)= 192 | ## used @: 193 | ## blur() = trigger(this, "change") = 194 | ## chooser.app.activeControll.trigger("change") 195 | if not this.disabled: 196 | var sb = SelectBox(this) 197 | sb.preval.deepCopy sb.options[] 198 | 199 | var parentWin = sb.app.activeWindow 200 | var win = sb.app.activeTile.newWindow() 201 | win.controlls.add(sb.chooser) 202 | var page = win.newPage() 203 | page.controlls.add(sb.chooser) 204 | sb.chooser.visible = true 205 | win.x1 = parentWin.x1 206 | win.y1 = parentWin.y1 + 1 207 | win.x2 = parentWin.x2 208 | win.y2 = parentWin.y2 209 | win.width = parentWin.width # win.x2 - win.x1 210 | win.heigth = win.y2 - win.y1 211 | win.styles["window"].bgColor[2]=235 212 | win.styles["window"].bgColor[3] = int(packRGB(38,38,38)) 213 | win.label = sb.label 214 | 215 | sb.chooser.win = win 216 | sb.chooser.x1 = win.x1 217 | sb.chooser.y1 = win.y1 + 1 218 | sb.chooser.x2 = win.x2 219 | sb.chooser.y2 = win.y2 220 | sb.chooser.width = sb.chooser.x2 - sb.chooser.x1 221 | sb.chooser.prevActiveControll = this 222 | 223 | for iC in 0..this.win.pages[this.win.currentPage].controlls.high : 224 | this.win.pages[this.win.currentPage].controlls[iC].visible = false 225 | win.draw() 226 | 227 | sb.app.activeControll = sb.chooser 228 | # setfocus!!!!!!!!! 229 | 230 | 231 | # ⎡ 232 | # ⎢ 233 | # ⎣ 234 | proc selectBoxOnChange*(this: Controll)= # …✔✖ ⚯ ⚮ ⚭ ⚬ 🂱 235 | if SelectBox(this).multiSelect: 236 | SelectBox(this).text = "" 237 | SelectBox(this).val = "" 238 | 239 | for opt in SelectBox(this).options[] : 240 | if opt.selected : 241 | if SelectBox(this).val != "": 242 | SelectBox(this).text = SelectBox(this).text & "," 243 | SelectBox(this).val = SelectBox(this).val & "," 244 | 245 | SelectBox(this).text = SelectBox(this).text & (if opt.name == "☓": "" else: opt.name)#opt.name 246 | SelectBox(this).val = SelectBox(this).val & opt.value 247 | else: 248 | for opt in SelectBox(this).options[] : 249 | if opt.selected : 250 | SelectBox(this).text = (if opt.name == "☓": "" else: opt.name) 251 | SelectBox(this).val = opt.value 252 | 253 | SelectBox(this).draw() 254 | #echo "CHANGE" 255 | 256 | 257 | 258 | 259 | proc onKeyPress(this: Controll, event:KMEvent)= 260 | if not this.disabled: 261 | if event.evType == "CtrlKey": 262 | case event.ctrlKey: 263 | of 13: 264 | this.onClick(this, event) 265 | else: 266 | discard 267 | 268 | 269 | proc newSelectBox*(win:Window, label: string, multiSelect:bool=false, width:int=20): SelectBox = 270 | result = new SelectBox 271 | result.label=label 272 | result.multiSelect = multiSelect 273 | result.width=width 274 | result.offset_h = 0 275 | #result.cursor_pos = 0 276 | result.visible = false 277 | result.disabled = false 278 | #result.width = width 279 | result.heigth = 2 280 | result.styles = newTable[string, StyleSheetRef](8) 281 | #[ result.styles.add("input",win.app.styles["input"]) 282 | result.activeStyle = result.styles["input"] ]# 283 | var styleNormal: StyleSheetRef = new StyleSheetRef 284 | styleNormal.deepcopy win.app.styles["input"] 285 | styleNormal.border="none" 286 | result.styles.add("input",styleNormal) 287 | result.activeStyle = result.styles["input"] 288 | 289 | result.app = win.app 290 | result.win = win 291 | 292 | result.listeners = @[] 293 | result.addEventListener("change", selectBoxOnChange) 294 | 295 | var styleFocused: StyleSheetRef = new StyleSheetRef 296 | styleFocused.deepcopy result.styles["input"] 297 | styleFocused.bgColor[2]=222 298 | styleFocused.bgColor[3] = int(packRGB(255,215,95)) 299 | styleFocused.textStyle.incl(styleItalic) 300 | result.styles.add("input:focus",styleFocused) 301 | 302 | var styleDragged: StyleSheetRef = new StyleSheetRef 303 | styleDragged.deepCopy result.styles["input"] 304 | styleDragged.bgColor[2]=128 305 | styleDragged.bgColor[3] = int(packRGB(175,0,215)) 306 | styleDragged.textStyle.incl(styleBlink) 307 | result.styles.add("input:drag",styleDragged) 308 | 309 | result.drawit = drawit 310 | 311 | result.options = new OptionListRef 312 | result.options[] = @[] 313 | result.options[].add((name:"☓" , value: "", selected:false)) 314 | result.chooser = newChooser(win,result.options, multiSelect) 315 | 316 | result.onClick = selectBoxOnClick 317 | result.onKeypress = onKeyPress 318 | result.focus = focus 319 | result.blur = blur 320 | result.cancel = cancel 321 | 322 | 323 | win.controlls.add(result) 324 | 325 | 326 | proc newSelectBox*(win:Window, label: string, multiSelect:bool=false, width:string): SelectBox = 327 | result = newSelectBox(win, label, multiSelect, width = 0) 328 | discard width.parseInt(result.width_value) 329 | 330 | 331 | -------------------------------------------------------------------------------- /src/stui/ui_textbox.nim: -------------------------------------------------------------------------------- 1 | #import stui, terminal, colors, colors_extra, unicode, tables, locks 2 | include "controll.inc.nim" 3 | 4 | type TextBox* = ref object of Controll 5 | #label*:string 6 | val*:string 7 | preval*:string # undo 8 | 9 | #width*:int # of input 10 | maxlength*:int 11 | 12 | offset_h*:int 13 | cursor_pos*:int 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | proc draw*(this: TextBox, updateOnly: bool = false) = 22 | if this.visible: 23 | acquire(this.app.termlock) 24 | 25 | if not updateOnly: 26 | setColors(this.app, this.win.activeStyle[]) 27 | terminal_extra.setCursorPos(this.x1 + this.activeStyle.margin.left, 28 | this.y1 + this.activeStyle.margin.top) 29 | stdout.write this.label 30 | 31 | 32 | # draw border 33 | drawBorder(this.activeStyle.border, 34 | this.x1 + this.activeStyle.margin.left, 35 | this.y1 + this.activeStyle.margin.top + 1 #[label]#, 36 | this.x2 - this.activeStyle.margin.right, 37 | this.y2 - this.activeStyle.margin.bottom 38 | ) 39 | #... 40 | 41 | # echo input area 42 | 43 | setColors(this.app, this.activeStyle[]) 44 | terminal_extra.setCursorPos(leftX(this), 45 | bottomY(this)) 46 | 47 | if this.val.runeLen > 0 : 48 | if this.val.runeLen < this.width: 49 | stdout.write this.val 50 | stdout.write " " * (this.width - this.val.runeLen) 51 | else: 52 | # text longer even w offset 53 | if this.offset_h + (this.width - 1) < this.val.runeLen: 54 | stdout.write this.val.runeSubStr(this.offset_h, this.width) 55 | else: 56 | var used = (this.val.runeLen - this.offset_h) 57 | stdout.write this.val.runeSubStr(this.offset_h, used) 58 | stdout.write " " * (this.width - used) 59 | else: 60 | stdout.write " " * this.width 61 | 62 | this.app.setCursorPos() 63 | release(this.app.termlock) 64 | 65 | 66 | ### MANDATORY ### 67 | proc drawit(this: Controll, updateOnly: bool = false){.gcsafe.} = 68 | TextBox(this).draw(updateOnly) 69 | 70 | 71 | 72 | 73 | 74 | proc `value=`*(this: TextBox, str:string) = 75 | this.val = str 76 | if this.visible: this.draw() 77 | trigger(this, "change") 78 | 79 | proc `value`*(this: TextBox): string = this.val 80 | 81 | # Textbox value and value2 same type as val : string 82 | proc `value2=`*(this: TextBox, str:string) = 83 | this.val = str 84 | if this.visible: this.draw() 85 | trigger(this, "change") 86 | 87 | proc `value2`*(this: TextBox): string = this.val 88 | 89 | 90 | 91 | 92 | proc onClick(this: Controll, event: KMEvent):void= 93 | if not this.disabled: 94 | trigger(this, "click") 95 | TextBox(this).draw() 96 | 97 | ### MEMOS ### 98 | #cast[ptr Textbox](event.target).val = "KLIKK" & $cast[ptr Textbox](event.target).y1 & " " & $cast[ptr Textbox](event.target).y2 99 | #draw(cast[ptr Textbox](event.target)[]) 100 | #[ cast[ptr Textbox](event.target.app.activeControll)[].val = "Klikkkkk" 101 | cast[ptr Textbox](event.target.app.activeControll)[].draw() ]# 102 | 103 | 104 | 105 | proc onDrop(this: Controll, event: KMEvent):void= 106 | #this.focus(this) # focus is done in stui.mouseeventhandler 107 | if not this.disabled: 108 | if event.source of TextBox: 109 | TextBox(this).val=Textbox(event.source).val 110 | #TextBox(this).focus(this) 111 | 112 | #of "[F": #End: 113 | if TextBox(this).val.runeLen > TextBox(this).width: 114 | TextBox(this).offset_h = TextBox(this).val.runeLen - TextBox(this).width + 1 115 | terminal_extra.setCursorPos(TextBox(this).rightX(), TextBox(this).bottomY()) 116 | this.app.cursorPos.x = TextBox(this).rightX() 117 | this.app.cursorPos.y = TextBox(this).bottomY() 118 | else: 119 | terminal_extra.setCursorPos(TextBox(this).leftX() + TextBox(this).val.runeLen, TextBox(this).bottomY()) 120 | this.app.cursorPos.x = TextBox(this).leftX() + TextBox(this).val.runeLen 121 | this.app.cursorPos.y = TextBox(this).bottomY() 122 | 123 | TextBox(this).cursor_pos = TextBox(this).val.runeLen 124 | TextBox(this).draw() 125 | 126 | 127 | 128 | proc onDrag(this: Controll, event: KMEvent)= 129 | #[ if this.app.activeControll != this: 130 | this.app.activate(this) ]# 131 | if not this.disabled: 132 | this.activeStyle = this.styles["input:drag"] 133 | TextBox(this).draw() 134 | 135 | 136 | proc focus(this: Controll)= 137 | this.prevStyle = this.activeStyle 138 | this.activeStyle = this.styles["input:focus"] 139 | 140 | ##editing mode 141 | #move curs to end 142 | #if longer than width - offset 143 | #handle input 144 | var tb = TextBox(this) 145 | tb.preval = tb.val # for undo 146 | 147 | if tb.val.runeLen >= tb.width: 148 | tb.offset_h = tb.val.runeLen - tb.width + 1 149 | this.app.setCursorPos(this.rightX(), this.bottomY() ) 150 | else: 151 | this.app.cursorPos.x = this.leftX() + tb.val.runeLen 152 | this.app.cursorPos.y = this.bottomY() 153 | 154 | tb.cursor_pos = tb.val.runeLen 155 | 156 | setCursorStyle(CursorStyle.steadyBlock) 157 | showCursor() 158 | 159 | 160 | proc blur(this: Controll)= 161 | if this.prevStyle != nil: # prevstyle may not initialized 162 | this.activeStyle = this.prevStyle 163 | #this.prevStyle = nil 164 | 165 | TextBox(this).offset_h = 0 166 | setCursorStyle(CursorStyle.steadyUnderline) 167 | hideCursor() 168 | trigger(this, "change") 169 | this.app.parkCursor() 170 | 171 | 172 | 173 | proc cancel*(this: Controll)= 174 | TextBox(this).val = TextBox(this).preval 175 | TextBox(this).blur(this) 176 | TextBox(this).draw() 177 | 178 | 179 | 180 | 181 | proc onKeyPress(this: Controll, event: KMEvent)= 182 | var tb = TextBox(this) 183 | if not this.disabled: 184 | if event.evType == "FnKey": #.....FnKey.....FnKey.....FnKey.....FnKey 185 | case event.key: 186 | of "[C": # right 187 | if tb.cursor_pos < tb.val.runeLen: 188 | tb.cursor_pos += 1 189 | # if right edge 190 | if tb.app.cursorPos.x == tb.rightX(): #tb.x1 + tb.width - 1 : 191 | tb.offset_h += 1 192 | tb.draw() 193 | #tb.app.setCursorPos() 194 | else: 195 | tb.app.cursorPos.x += 1 196 | tb.app.setCursorPos() 197 | 198 | of "[D": # left 199 | if tb.cursor_pos > 0: 200 | tb.cursor_pos -= 1 201 | # if left edge 202 | if tb.app.cursorPos.x == tb.leftX(): 203 | tb.offset_h -= 1 204 | tb.draw() 205 | #tb.app.setCursorPos() 206 | else: 207 | tb.app.cursorPos.x -= 1 208 | tb.app.setCursorPos() 209 | 210 | of "[F": #End 211 | 212 | if tb.val.runeLen > tb.width: 213 | tb.offset_h = tb.val.runeLen - tb.width + 1 214 | terminal_extra.setCursorPos(tb.rightX(), tb.bottomY()) 215 | this.app.cursorPos.x = tb.rightX() 216 | this.app.cursorPos.y = tb.bottomY() 217 | else: 218 | terminal_extra.setCursorPos(tb.leftX() + tb.val.runeLen, tb.bottomY()) 219 | this.app.cursorPos.x = tb.leftX() + tb.val.runeLen 220 | this.app.cursorPos.y = tb.bottomY() 221 | 222 | tb.cursor_pos = tb.val.runeLen 223 | tb.draw() 224 | #tb.app.setCursorPos() 225 | 226 | 227 | of "[H": #Home 228 | tb.offset_h = 0 229 | tb.cursor_pos = 0 230 | this.app.cursorPos.x = tb.leftX() 231 | this.app.cursorPos.y = tb.bottomY() 232 | tb.draw() 233 | #tb.app.setCursorPos() 234 | 235 | else: discard 236 | 237 | elif event.evType == "Char": #......Char......Char......Char......Char 238 | if tb.maxlength == 0 or tb.maxlength >= tb.val.runeLen : 239 | # add to begin or insert at point 240 | if tb.cursor_pos > 0 : 241 | tb.val = tb.val.runeSubStr(0, tb.cursor_pos ) & event.key & tb.val.runeSubStr(tb.cursor_pos , tb.val.runeLen) 242 | elif tb.cursor_pos == 0: 243 | tb.val = event.key & tb.val 244 | elif tb.cursor_pos == tb.val.runeLen: 245 | tb.val &= event.key 246 | 247 | tb.cursor_pos += 1 248 | 249 | # insert: scrolls text 250 | if #[ tb.offset_h > 0 or ]# this.app.cursorPos.x == tb.rightX(): 251 | tb.offset_h += 1 252 | 253 | #[ elif this.app.cursorPos.x < tb.rightX(): 254 | tb.app.cursorPos.x += 1 ]# 255 | else: 256 | tb.app.cursorPos.x += 1 257 | 258 | 259 | tb.draw() 260 | #tb.app.setCursorPos() 261 | 262 | elif event.evType == "CtrlKey": #.....CtrlKey.....CtrlKey.....CtrlKey 263 | case event.ctrlKey: 264 | of 13: # enter 265 | this.blur(this) 266 | TextBox(this).draw() 267 | of 127: # del 268 | if tb.cursor_pos > 0 : # if something to delete 269 | tb.val = tb.val.runeSubStr(0, tb.cursor_pos - 1 ) & tb.val.runeSubStr(tb.cursor_pos) 270 | 271 | tb.cursor_pos -= 1 272 | 273 | if tb.val.runeLen < tb.width: # if no pageing 274 | this.app.cursorPos.x = this.leftX() + tb.cursor_pos 275 | tb.offset_h = 0 #! 276 | else: # if long text 277 | if tb.offset_h > 0 : # if offsetted move offset 278 | tb.offset_h -= 1 279 | #[ setCursorPos(4,1) 280 | echo tb.offset_h ]# 281 | else: # set cursor pos 282 | this.app.cursorPos.x = this.leftX() + tb.cursor_pos 283 | else: echo " -impossible- " 284 | 285 | 286 | tb.draw() 287 | #tb.app.setCursorPos() 288 | 289 | else: 290 | discard 291 | #echo "ctrl" 292 | 293 | else: # if this.disabled: 294 | if event.evType == "CtrlKey": 295 | case event.ctrlKey: 296 | of 3: 297 | discard 298 | #CLIPBOARD 299 | else: 300 | discard 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ######## # # ###### # # 309 | ######## ## # # # # 310 | ######## # # # ##### # # 311 | ######## # # # # # ## # 312 | ######## # ## # ## ## 313 | ######## # # ###### # # 314 | 315 | proc newTextBox*(win:Window, label: string, width:int=20, maxlength:int=20): TextBox = 316 | result = new TextBox 317 | result.label=label 318 | #result.width=width 319 | result.maxlength=maxlength 320 | result.offset_h = 0 321 | result.cursor_pos = 0 322 | result.visible = false 323 | result.disabled = false 324 | result.width = width 325 | result.heigth = 2 326 | result.styles = newTable[string, StyleSheetRef](8) 327 | 328 | var styleNormal: StyleSheetRef = new StyleSheetRef 329 | styleNormal.deepcopy win.app.styles["input"] 330 | styleNormal.border="none" 331 | result.styles.add("input",styleNormal) 332 | result.activeStyle = result.styles["input"] 333 | 334 | var styleFocused: StyleSheetRef = new StyleSheetRef 335 | styleFocused.deepcopy win.app.styles["input:focus"] 336 | result.styles.add("input:focus",styleFocused) 337 | 338 | var styleDragged: StyleSheetRef = new StyleSheetRef 339 | styleDragged.deepCopy win.app.styles["input:drag"] 340 | result.styles.add("input:drag",styleDragged) 341 | 342 | var styleDisabled: StyleSheetRef = new StyleSheetRef 343 | styleDisabled.deepcopy win.app.styles["input:disabled"] 344 | result.styles.add("input:disabled", styleDisabled) 345 | 346 | result.drawit = drawit 347 | result.blur = blur 348 | result.focus = focus 349 | result.onClick = onClick 350 | result.onDrag = onDrag 351 | result.onDrop = onDrop 352 | result.cancel = cancel 353 | result.onKeypress = onKeyPress 354 | 355 | result.app = win.app 356 | result.win = win 357 | result.listeners = @[] 358 | 359 | win.controlls.add(result) 360 | 361 | 362 | proc newTextBox*(win:Window, label: string, width:string, maxlength:int=20): TextBox = 363 | result = newTextBox(win, label, width=0, maxlength) 364 | (result.width_unit, result.width_value) = parseSizeStr(width) --------------------------------------------------------------------------------