Compare commits

...

281 Commits

Author SHA1 Message Date
Jeff Carr 3acf473792 quiet more startup noise 2025-03-10 23:38:43 -05:00
Jeff Carr 6c3149c0fe quiet output 2025-03-10 23:27:14 -05:00
Jeff Carr 784eac740f testing code 2025-03-05 03:00:11 -06:00
Jeff Carr a88937c508 working on delete table 2025-03-04 14:33:39 -06:00
Jeff Carr 96eac58cf5 name change libnotify 'menu' to 'icon' 2025-03-04 13:35:13 -06:00
Jeff Carr b8781b1b64 remove debugging delays 2025-03-04 04:32:22 -06:00
Jeff Carr 126b9a30a2 faster is okay now 2025-03-04 04:29:27 -06:00
Jeff Carr 994449b1c2 lock for now for gocui 2025-03-04 04:06:36 -06:00
Jeff Carr 5ebb13a454 add a frozen channel 2025-03-04 01:58:13 -06:00
Jeff Carr 6127fa1cbb try out sync.Once 2025-03-03 23:50:11 -06:00
Jeff Carr 54bbe72aa8 SIGWINCH works on background and libnotify widgets 2025-03-03 22:56:05 -06:00
Jeff Carr b373eab346 set help window offset 2025-03-03 22:21:39 -06:00
Jeff Carr e73cfaf490 by default, hide help menu 2025-03-03 18:41:34 -06:00
Jeff Carr 046e6b4d6c mouse clicks work on libnotify menu 2025-03-03 18:36:50 -06:00
Jeff Carr efb5ede4b9 libnotify menu at right height 2025-03-03 16:37:45 -06:00
Jeff Carr 5e6d7cffdf clock works again 2025-03-03 15:00:27 -06:00
Jeff Carr 76e15fa1df start looking into proper mutex locking 2025-03-03 11:59:45 -06:00
Jeff Carr 75014f4b28 define a background widget more properly 2025-03-03 08:31:21 -06:00
Jeff Carr 9ef16c1bf2 better tree init() 2025-03-03 03:45:36 -06:00
Jeff Carr da54c0f039 always write to /tmp/ also 2025-03-03 02:31:12 -06:00
Jeff Carr 6d1dfed3db set global options in init 2025-03-03 02:12:54 -06:00
Jeff Carr 65cf744a86 cleaner libnotifyUpdate() 2025-03-03 01:51:16 -06:00
Jeff Carr caf7428ba3 generic init debugging area 2025-03-03 01:24:12 -06:00
Jeff Carr 948d2af071 rm more wrong code 2025-03-03 01:19:01 -06:00
Jeff Carr ddd7709182 start rewinding all the other dumb init code 2025-03-03 01:16:54 -06:00
Jeff Carr 9912c3eb82 stdout init almost finally works 2025-03-03 01:07:27 -06:00
Jeff Carr 660d9e7e3a init is more correct now 2025-03-03 01:00:11 -06:00
Jeff Carr 0124d25c34 more work on a better Init() 2025-03-03 00:51:07 -06:00
Jeff Carr ed3789c23f jesus. I never even made a ToolkitInit() 2025-03-03 00:12:40 -06:00
Jeff Carr 6f739933b7 always update clock 2025-03-02 20:05:58 -06:00
Jeff Carr 4b79e862a7 some crippled dumb refresh code kinda makes init() work for now 2025-03-02 18:16:56 -06:00
Jeff Carr dc329ed18c can refresh manually 2025-03-02 18:04:08 -06:00
Jeff Carr 3e7287baea better, but still wrong dropdown menu sizes 2025-03-02 17:44:17 -06:00
Jeff Carr 04406b3561 support a libnotify menu 2025-03-02 17:05:17 -06:00
Jeff Carr 0638183356 start moving to standard internal TK functions 2025-03-02 13:34:09 -06:00
Jeff Carr f24c509859 align 1st window. start libnotify 2025-03-02 12:04:59 -06:00
Jeff Carr a000c06987 help menu was wrong. thanks bmath 2025-02-23 23:15:31 -06:00
Jeff Carr 3b9e5beff6 'q' doesn't work 2025-02-23 18:57:40 -06:00
Jeff Carr ad34230d67 starting the table window 2025-02-19 17:39:45 -06:00
Jeff Carr 57b6efd831 add the protobuf to the struct 2025-02-19 17:39:45 -06:00
Jeff Carr f36f9cfd0f only a few node references left 2025-02-19 17:39:45 -06:00
Jeff Carr a5800917e8 still more 2025-02-19 17:39:45 -06:00
Jeff Carr c64592f326 more and more 2025-02-19 17:39:45 -06:00
Jeff Carr 377b08eeb6 protobuf stuff 2025-02-19 17:39:45 -06:00
Jeff Carr 22e139e2e5 protobuf changes 2025-02-19 17:39:45 -06:00
Jeff Carr 6b7fafbde2 protobuf changes 2025-02-19 17:39:45 -06:00
Jeff Carr 3f09b2b6e4 refactor for protobuf 2025-02-19 17:39:45 -06:00
Jeff Carr 31324ad083 string rows 2025-02-19 17:39:45 -06:00
Jeff Carr ad299911f1 delete from the gocui binary tree 2025-02-19 17:39:45 -06:00
Jeff Carr 3d1bb9680a close kinda working 2025-02-19 17:39:45 -06:00
Jeff Carr 9cd1d582e2 window close works 2025-02-19 17:39:45 -06:00
Jeff Carr 3ca1fff755 mouse scroll in stdout 2025-02-13 22:54:57 -06:00
Jeff Carr 5a9f3565aa protect against another nil 2025-02-13 22:24:34 -06:00
Jeff Carr 1f33979af9 add Disable() and Enable() 2025-02-13 21:04:37 -06:00
Jeff Carr 1e79d31a02 subbed in tables 2025-02-13 20:10:55 -06:00
Jeff Carr 08a5b198c7 no more newaction(). finally. 2025-02-13 15:04:28 -06:00
Jeff Carr c215e3c2c4 still moving everything to initPlugin() 2025-02-13 14:22:33 -06:00
Jeff Carr c5472a42a2 finally starting to clean up the init() process 2025-02-13 14:11:52 -06:00
Jeff Carr fa9ec36dbb initOnce() 2025-02-12 17:00:32 -06:00
Jeff Carr 8c459da9f7 add some notes 2025-02-12 15:43:40 -06:00
Jeff Carr 552bdeb1e6 plugin related cleanups 2025-02-12 15:26:24 -06:00
Jeff Carr 0a60272440 working on button disable 2025-02-12 00:19:15 -06:00
Jeff Carr a575a08bcc disable me.textbox() output 2025-02-11 13:55:17 -06:00
Jeff Carr 57e5ff22dc minor 2025-02-11 13:23:50 -06:00
Jeff Carr 733c595c54 disable and enable doesn't crash 2025-02-11 13:22:23 -06:00
Jeff Carr 535646335a rm debugging output 2025-02-10 23:43:08 -06:00
Your Name 8d24366492 early debugging code 2024-01-01 12:00:00 -06:00
Jeff Carr 6df064282c attempting to force the "Running..." box in the corner 2025-02-09 17:31:32 -06:00
Jeff Carr 6ea6ffaa3d disable the whole screen while things are waiting 2025-02-09 14:35:11 -06:00
Jeff Carr 7d793c68db disable window paging code for now 2025-02-09 13:33:26 -06:00
Jeff Carr b07d8bd8f7 rename 2025-02-09 13:09:42 -06:00
Jeff Carr 42758e1459 junk 2025-02-09 13:09:22 -06:00
Jeff Carr f30489219b color cleanups 2025-02-09 13:08:00 -06:00
Jeff Carr bb2732b621 color code cleanups 2025-02-09 12:57:42 -06:00
Jeff Carr 00d1256eba mouse code is better than it's ever been 2025-02-09 12:47:49 -06:00
Jeff Carr cf073e9aae more mouse code cleanups 2025-02-09 12:43:34 -06:00
Jeff Carr 90a9f84f10 more code cleanups 2025-02-09 12:34:53 -06:00
Jeff Carr c5cada3dc9 code cleanups 2025-02-09 12:21:43 -06:00
Jeff Carr 87d31a3d94 stub in code to page large windows 2025-02-09 09:00:40 -06:00
Jeff Carr c5d9522c0b clip large windows 2025-02-09 08:28:10 -06:00
Jeff Carr 4a009f79a2 fix panic in Enable() and Disable() 2025-02-09 06:45:01 -06:00
Jeff Carr 70452bdaac buttons disabled on window open 2025-02-09 06:21:21 -06:00
Jeff Carr 36745e0492 button disable maybe works? 2025-02-09 06:14:57 -06:00
Jeff Carr 9540c01d83 cleaning up color handling for enable/disable 2025-02-09 05:17:01 -06:00
Jeff Carr 58eff2a9e2 disable enable is starting to display 2025-02-09 04:53:49 -06:00
Jeff Carr c00084bf3f better window title colors 2025-02-09 04:33:30 -06:00
Jeff Carr 9a08b37be4 better window titlebar 2025-02-09 04:19:32 -06:00
Jeff Carr 955afcb1a9 text boxes are forced 5 spaces wide 2025-02-09 04:03:48 -06:00
Jeff Carr af3fec6f20 remove old debugging code 2025-02-09 03:24:10 -06:00
Jeff Carr 5bac0308e5 window close works 2025-02-09 03:21:48 -06:00
Jeff Carr 8d8fc22745 detect an attempt to close a window 2025-02-09 03:00:10 -06:00
Jeff Carr eba5ea8cc0 leave this code as a reminder for later 2025-02-09 01:46:04 -06:00
Jeff Carr b8b8a409ea show stdout buffer when it is small 2025-02-09 01:40:57 -06:00
Jeff Carr 010bd2a33f just remove this I guess 2025-02-08 21:31:04 -06:00
Jeff Carr ba629f1892 things resized 2025-02-08 18:43:48 -06:00
Jeff Carr bff0943dc5 keep cleaning the 'msg' stdout handling code 2025-02-08 18:21:31 -06:00
Jeff Carr 2c07da350a resize window after text thing worked 2025-02-08 18:09:09 -06:00
Jeff Carr 82ed687460 mouse drag was not always right 2025-02-08 18:03:11 -06:00
Jeff Carr 5a84456c7a more usability cleanups 2025-02-08 17:19:41 -06:00
Jeff Carr f8b7c603a1 <enter> closes and saves the text from the textbox 2025-02-08 16:23:03 -06:00
Jeff Carr 419f4aef6a more work on the text entry 2025-02-08 16:15:38 -06:00
Jeff Carr 12d0e185cc text entry worked for the first time 2025-02-08 16:02:00 -06:00
Jeff Carr e80827d890 stuff 2025-02-08 15:16:41 -06:00
Jeff Carr 42eafb87c7 dropdown works again 2025-02-08 15:01:36 -06:00
Jeff Carr ea544e429e setting the size of the textbox correctly 2025-02-08 14:43:38 -06:00
Jeff Carr 58cb7f3d2d still can't remove "msg" create in mouseMove() 2025-02-08 14:24:43 -06:00
Jeff Carr 53eb14ccbd better filename 2025-02-08 14:09:14 -06:00
Jeff Carr 23dfd96a87 changing Height() to reflect more realistic values 2025-02-08 14:04:11 -06:00
Jeff Carr 5827b9ace2 buttons in dense mode line up correctly 2025-02-08 13:57:31 -06:00
Jeff Carr c4f9bac85e add config default to show stdout onscreen on start 2025-02-08 13:28:19 -06:00
Jeff Carr 90083d5bcb a clock 2025-02-08 12:50:05 -06:00
Jeff Carr 77b4bcb94b quiet refresh goroutine 2025-02-08 12:13:12 -06:00
Jeff Carr 665d2289e2 tiggers gocui on startup 2025-02-08 12:05:25 -06:00
Jeff Carr ed024aac70 textbox opens 2025-02-08 10:30:19 -06:00
Jeff Carr 6045a205fd add 'Esc' key to close dropdown menu 2025-02-08 10:20:00 -06:00
Jeff Carr 227419c1ad dropdown menu works again 2025-02-08 10:00:12 -06:00
Jeff Carr 7b06b81ed3 double click windows brings to front 2025-02-08 09:25:09 -06:00
Jeff Carr 9d1a045a1f window double click brings to forefront 2025-02-08 09:16:22 -06:00
Jeff Carr 1923f8df96 mouse dragging works correctly again 2025-02-08 09:12:35 -06:00
Jeff Carr b730ee9459 double click stdout to move to front or back 2025-02-08 08:55:26 -06:00
Jeff Carr a6c1864f43 removed old var 2025-02-08 08:47:55 -06:00
Jeff Carr 1010db44a6 mouse double click implemented too. why not? took 5 minutes 2025-02-08 08:42:41 -06:00
Jeff Carr 078a23e0e0 detect double click 2025-02-08 08:36:08 -06:00
Jeff Carr 44264df09d mouse click vs drag works 2025-02-08 08:12:39 -06:00
Jeff Carr 0aa82f5ba5 trying to delay on mouse drag 2025-02-08 08:07:03 -06:00
Jeff Carr 83b4d7142a window setTitle() 2025-02-08 07:29:42 -06:00
Jeff Carr fefa99920b global protobuf name conflict 2025-02-08 06:34:51 -06:00
Jeff Carr 8185d8bc1a macos iterm2 only seems to work with dark mode 2025-02-08 05:16:05 -06:00
Jeff Carr 481fa11211 set stdout in a better place 2025-02-07 19:11:20 -06:00
Jeff Carr a295aa420b remember stdout location on restore 2025-02-07 16:44:01 -06:00
Jeff Carr d8353f9b1a maybe stable? 2025-02-07 12:42:36 -06:00
Jeff Carr b6b5df6a18 forge is finally an app again. this time in the console 2025-02-07 03:51:23 -06:00
Jeff Carr 13a194dca5 more on dark mode 2025-02-07 03:26:05 -06:00
Jeff Carr 6c522a4b27 dark mode is okay 2025-02-07 03:19:36 -06:00
Jeff Carr dd5232fa6b more on a dark mode 2025-02-07 03:09:16 -06:00
Jeff Carr 6ac82df949 might as well test dark/light mode 2025-02-07 02:49:11 -06:00
Jeff Carr 0b67b198bd finally drag works everywhere 2025-02-07 02:44:52 -06:00
Jeff Carr 7813fc126d maybe fixed wierd clicks on window drag 2025-02-07 02:34:40 -06:00
Jeff Carr fb3c16707d start closing down crappy color choices 2025-02-07 01:55:27 -06:00
Jeff Carr 5668e6f081 textbox kinda works 2025-02-07 00:35:08 -06:00
Jeff Carr e96cb4375c code cleanups 2025-02-07 00:08:44 -06:00
Jeff Carr c2f8cac4a9 basic start of a stubbed out 'textbox' entry box 2025-02-06 23:49:18 -06:00
Jeff Carr 7e47ca9843 quiet output 2025-02-06 23:35:15 -06:00
Jeff Carr 37723c2b9a stdout still doesn't work with the mouse correctly 2025-02-06 22:26:47 -06:00
Jeff Carr 16886945ed init was the problem for window placement 2025-02-06 22:17:05 -06:00
Jeff Carr 3d104d5b4a dragging and window placement is still messed up 2025-02-06 21:25:51 -06:00
Jeff Carr 0b265d2b72 stdout paging updates 2025-02-06 20:59:15 -06:00
Jeff Carr 5d1a3b2578 finally. dense window view 2025-02-06 20:34:29 -06:00
Jeff Carr e39bcafb78 closer to accurate grid sizes 2025-02-06 19:58:48 -06:00
Jeff Carr 176831d0f3 trying to debug grid spacing 2025-02-06 19:34:26 -06:00
Jeff Carr 2a5734892a trying to fix Hidden() to use the parent 2025-02-06 19:00:00 -06:00
Jeff Carr f5d465901d trying to send Close Window() from toolkit plugin 2025-02-06 17:29:17 -06:00
Jeff Carr e134ecb6a4 make stdout upside down 2025-02-06 16:19:36 -06:00
Jeff Carr 5bae8b7e41 fixed window title string length 2025-02-06 15:19:39 -06:00
Jeff Carr 93e87a05c7 dragging a windows keeps it put 2025-02-06 14:46:32 -06:00
Jeff Carr bc15e6c879 simple cleanups 2025-02-06 14:35:01 -06:00
Jeff Carr 1918dcbbde no output except 'mouse drag()' 2025-02-06 14:19:43 -06:00
Jeff Carr 5675307497 quiet output 2025-02-06 14:13:31 -06:00
Jeff Carr 88f33afbb7 window depth order works 2025-02-06 13:47:19 -06:00
Jeff Carr 9fa974f6c4 continue improvements on window dragging 2025-02-06 09:30:54 -06:00
Jeff Carr c4095ef7aa not sure why mouse clicks are working weird 2025-02-06 07:39:31 -06:00
Jeff Carr c136ca2b4c tab rotates through the windows 2025-02-06 07:01:27 -06:00
Jeff Carr 87141b8d99 dropdown menus work pretty well 2025-02-06 05:52:00 -06:00
Jeff Carr 6d991fef63 dropdown menus are maybe working again 2025-02-06 05:41:51 -06:00
Jeff Carr 9c7b139e5a full screen BG widget is created. good enough to ship it 2025-02-06 04:47:50 -06:00
Jeff Carr d3b25092f8 resize stdout window works 2025-02-06 04:15:36 -06:00
Jeff Carr 96cb52f3ef stdout window hides on startup 2025-02-06 03:33:27 -06:00
Jeff Carr 9c548faeda try to start with STDOUT offscreen 2025-02-06 03:28:05 -06:00
Jeff Carr d2c3db7b58 more work on stdout settings 2025-02-06 03:09:13 -06:00
Jeff Carr d0e35bb98f rm more old code 2025-02-06 02:54:50 -06:00
Jeff Carr 8522d4671e stdout var cleanup 2025-02-06 02:49:21 -06:00
Jeff Carr 88e9594b93 removing old crappy code finally 2025-02-06 02:40:44 -06:00
Jeff Carr 3faacd6c43 nicer help menu & stdout behavior 2025-02-06 02:15:21 -06:00
Jeff Carr 31c130045d add a goroutine and channel to trigger window redraw 2025-02-06 01:42:40 -06:00
Jeff Carr 70a742c98a move up the primary box 2025-02-05 17:53:57 -06:00
Jeff Carr 546a4e022b minor rm 2025-02-05 17:31:56 -06:00
Jeff Carr efebe00640 window dragging works fairly well 2025-02-05 16:30:06 -06:00
Jeff Carr 07f6b7842e add debugWindow() 2025-02-05 15:55:56 -06:00
Jeff Carr 83e9787e75 window drag offset works 2025-02-05 15:04:40 -06:00
Jeff Carr 85eda6aeb8 actually there. fire in the hole 2025-02-05 14:48:35 -06:00
Jeff Carr 8dd0f88e7c almost there. as in Star Wars almost there 2025-02-05 14:42:50 -06:00
Jeff Carr c328a755c6 windowFrame is below window widget correctly 2025-02-05 14:29:38 -06:00
Jeff Carr 3fa508f786 add windowFrame widget for windows 2025-02-05 14:11:37 -06:00
Jeff Carr d75bfa639c two windows drag at a time 2025-02-05 12:51:00 -06:00
Jeff Carr f1eefc9a06 move code to better homes 2025-02-05 12:32:41 -06:00
Jeff Carr 6a0fd773f4 finally window drag works 2025-02-05 11:58:27 -06:00
Jeff Carr ec68f448af dragging sometimes 2025-02-05 11:51:14 -06:00
Jeff Carr ae339cc587 have to fix gocui label sizes elsewhere 2025-02-05 11:27:12 -06:00
Jeff Carr 38a08d66fc closer 2025-02-05 11:08:15 -06:00
Jeff Carr fb8d1d0940 draws widgets in the right order but wrong place 2025-02-05 10:41:10 -06:00
Jeff Carr 12f3d5ac5c set the node.State.Label on SetText() 2025-02-05 07:24:14 -06:00
Jeff Carr a81e931b9a still notsure 2025-02-04 15:48:14 -06:00
Jeff Carr 6237bf89c3 hmm. still no, but more something or other 2025-02-04 14:54:24 -06:00
Jeff Carr 8ce9ca882a still a no 2025-02-04 14:45:23 -06:00
Jeff Carr bf8cbddf1a add findWindows() 2025-02-04 14:27:32 -06:00
Jeff Carr 4e5c6f2515 rm old stuff 2025-02-04 14:04:40 -06:00
Jeff Carr 3cf873439d hmm. still no dice 2025-02-04 13:31:48 -06:00
Jeff Carr 652cf2b73b misc code cleanups 2025-02-04 12:45:52 -06:00
Jeff Carr 1867bae62c hmm. still stumped. added tk.full 2025-02-04 11:33:10 -06:00
Jeff Carr 012273d8d3 misc 2025-02-04 11:12:13 -06:00
Jeff Carr 9d5cd2c865 somethings still wrong somewhere 2025-02-04 10:14:00 -06:00
Jeff Carr acfb80a2e7 kinda, sorta, but no. not yet 2025-02-04 10:04:45 -06:00
Jeff Carr a10582c846 closer 2025-02-04 09:52:46 -06:00
Jeff Carr 5a28806bdf getting closer on windows 2025-02-04 09:40:51 -06:00
Jeff Carr d2c681f573 windows kinda move around 2025-02-03 18:50:17 -06:00
Jeff Carr acb0e43945 notsure 2025-02-03 15:47:50 -06:00
Jeff Carr 65622d01cd compute window size 2025-02-03 15:29:44 -06:00
Jeff Carr d61e03b877 working on window drag 2025-02-03 10:55:50 -06:00
Jeff Carr f0d403e834 window labels drag 2025-02-03 10:21:43 -06:00
Jeff Carr e627e4e822 cleaning up debugging 2025-02-03 09:53:48 -06:00
Jeff Carr c91733e10d both widgets drag 2025-02-03 09:39:15 -06:00
Jeff Carr 6370d87fc2 closer 2025-02-03 01:11:01 -06:00
Jeff Carr c5e6c66338 can almost drag two different things 2025-02-03 00:07:48 -06:00
Jeff Carr a5b3a934d2 finally can drag something else 2025-02-02 23:36:33 -06:00
Jeff Carr 517d844b3c more work on the ever illusive floating stdout window 2025-02-02 15:04:47 -06:00
Jeff Carr 4a8fb6ab22 well, this needs more thought 2025-02-01 21:59:48 -06:00
Jeff Carr ac9c6617e3 closer to mouse drag not being annoyingly wrong 2025-02-01 21:28:52 -06:00
Jeff Carr d5d0262013 hmm. keep trying to debug dragging views 2025-02-01 21:01:03 -06:00
Jeff Carr 3125bbb258 dragged a label kinda. exposed the real problems 2025-02-01 20:13:08 -06:00
Jeff Carr a069e6c984 got stdout window to grab from a label 2025-02-01 19:56:02 -06:00
Jeff Carr d6f2fd983e better colors on labels and checkboxes 2025-02-01 19:42:04 -06:00
Jeff Carr e3c874cd69 help view opens on startup (kinda) 2025-02-01 19:00:15 -06:00
Jeff Carr eccec3ef1a no more dropdownV 2025-02-01 18:48:08 -06:00
Jeff Carr 666d5ca52d almost there red 5 2025-02-01 18:37:04 -06:00
Jeff Carr 0355f7c2fb huh. getting somewhere 2025-02-01 18:27:42 -06:00
Jeff Carr f79cf89170 part way to a generalized mouse move() 2025-02-01 18:05:39 -06:00
Jeff Carr 5a2097d080 trying stuff that isn't working 2025-02-01 17:58:13 -06:00
Jeff Carr 9a3f9d0991 kinda don't believe it, but maybe new mouseMove() 2025-02-01 17:38:10 -06:00
Jeff Carr 2062060dac hmm. notsure 2025-02-01 17:17:32 -06:00
Jeff Carr 7557486b13 found the code and renamed things 2025-02-01 17:03:14 -06:00
Jeff Carr 4dad234532 start working on movingMsg 2025-02-01 16:50:01 -06:00
Jeff Carr 3d5ee3f89b this gocui package is really cool 2025-02-01 16:44:43 -06:00
Jeff Carr 2e0465e44a silence all dropdown menu output 2025-02-01 15:57:29 -06:00
Jeff Carr 49f8e1c043 dropdown menu selects and changes text again 2025-02-01 15:48:28 -06:00
Jeff Carr 4bc9fa41b7 more cleanups 2025-02-01 15:19:35 -06:00
Jeff Carr 96fdfd1338 remove dumb old stuff and clean up struct.go 2025-02-01 15:15:05 -06:00
Jeff Carr 8d007ec10d trying to handle Flag widget clicks 2025-02-01 14:55:25 -06:00
Jeff Carr fdca4d2601 finally found the minor missing return lines 2025-02-01 14:02:58 -06:00
Jeff Carr 417b3e5225 use dumpWidget() where possible 2025-02-01 13:58:53 -06:00
Jeff Carr e4f0524bdf lots of code cleanup and debugging work 2025-02-01 13:43:51 -06:00
Jeff Carr ebb03139bb can't use protobuf in a plugin yet 2025-02-01 12:52:25 -06:00
Jeff Carr 4695ada409 oops 2025-02-01 12:23:02 -06:00
Jeff Carr a78cd82dcd GPL 3.0 2025-02-01 11:42:31 -06:00
Jeff Carr a8a918655a add keypress '2' as a debugging tool 2025-01-31 22:56:05 -06:00
Jeff Carr 39e851c76c very quiet output now with good debugging options 2025-01-31 22:08:29 -06:00
Jeff Carr bac14a675b lots of work to clean up my old code 2025-01-31 22:08:21 -06:00
Jeff Carr b7cd6d07fc hmm. can't figure out where clicks are going 2025-01-31 13:47:45 -06:00
Jeff Carr f76960c907 almost quiet 2025-01-31 13:47:45 -06:00
Jeff Carr 5b39848b64 more helpful and easier to read debugging output 2025-01-31 13:47:45 -06:00
Jeff Carr bbdf7fefbd make keyboard 'f' show what widgets are under the mouse 2025-01-31 13:47:45 -06:00
Jeff Carr 73de9899a8 hmm. mouse click detection is better. now what? 2025-01-31 13:47:45 -06:00
Jeff Carr c348940ca1 comments and code rearrangement 2025-01-31 13:47:45 -06:00
Jeff Carr 8a4afa760d the strings show up
Signed-off-by: Jeff Carr <jcarr@wit.com>
2025-01-31 13:47:45 -06:00
Jeff Carr 4aef276b64 toggle on the me.dropdown widget works 2025-01-31 13:47:45 -06:00
Jeff Carr 1d32c5da2b got the strings for a dropdown widget (finally. only took me 4 years) 2025-01-31 13:47:45 -06:00
Jeff Carr 9f38585892 I'm trying to make this clearer to understand 2025-01-31 13:47:45 -06:00
Jeff Carr 1a1881aa4e name changes after I haven't looked at this code for some time 2025-01-31 13:47:45 -06:00
Jeff Carr 75a5c7bf72 wrapping my head around this code again 2025-01-31 13:47:45 -06:00
Jeff Carr 11465e4043 ignore patch files 2025-01-29 12:28:03 -06:00
Jeff Carr 57fbbc62ed better debugging output 2025-01-29 12:27:10 -06:00
Jeff Carr ce11f999f9 start moving to protobuf. fix mouse clicks (kinda) 2025-01-29 12:27:10 -06:00
Jeff Carr a16b53c289 start debugging why this doesn't work 2025-01-29 12:27:10 -06:00
Jeff Carr b5fe1eb8ff standard build rules 2024-12-06 01:51:45 -06:00
Jeff Carr 85f3a08238 correct build rules. attempt ldflags VERSION 2024-12-05 19:15:34 -06:00
Jeff Carr 439c6e3b01 testing plugins 2024-12-05 18:49:22 -06:00
Jeff Carr 19370790f8 todo: fix golang plugin install
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-12-03 00:46:32 -06:00
Jeff Carr bab555aa4c notes to trigger build 2024-12-02 09:00:40 -06:00
Jeff Carr 7e2d7f72ef I guess this for now. todo: "package plugin" 2024-12-02 05:17:02 -06:00
Jeff Carr 39eb2e5210 add 'make install' for testing 2024-12-01 00:50:07 -06:00
Jeff Carr bb3802857b try adding //go:plugin to golang 2024-11-14 21:48:44 -06:00
Jeff Carr c02399708e stdout window much closer
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-14 10:05:43 -06:00
Jeff Carr aea18d5b65 still not right, but better stdout window 2024-11-14 09:50:27 -06:00
Jeff Carr 820f27c0a2 use io.Writer 2024-11-14 04:59:09 -06:00
Jeff Carr 15666309ee fakefile writes to stdout window work
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-14 04:28:12 -06:00
Jeff Carr aae13f2b57 more debugging
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-14 03:58:18 -06:00
Jeff Carr 2461df0153 output window works with keypress 'L'
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-14 03:50:59 -06:00
Jeff Carr 368c25107a test writes to stdout widget kinda work 2024-11-13 21:32:44 -06:00
Jeff Carr 2ace17294c try to use the stdout view
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-13 21:03:50 -06:00
Jeff Carr 6d3dded68b dump current colors in the shell
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-03 07:06:36 -05:00
Jeff Carr 32619e7dad link to blog about terminal color
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-03 06:58:04 -05:00
Jeff Carr 2ea283ea74 keep colors super simple for xterm support 2024-02-29 19:18:00 -06:00
44 changed files with 4395 additions and 2051 deletions

3
.gitignore vendored
View File

@ -1,4 +1,7 @@
*.swp
*.so
*.pb.go
*.patch
go.mod
go.sum
gocui

1
.plugin Normal file
View File

@ -0,0 +1 @@
// plugin

View File

@ -1,11 +1,31 @@
all: plugin
ldd ../gocui.so
VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d)
plugin:
GO111MODULE=off go build -v -buildmode=plugin -o ../gocui.so
all: clean goimports vet gocui
@ldd gocui.so
pluginreal:
go build -v -buildmode=plugin -o ~/go/lib/toolkits/gocui.so
vet:
@GO111MODULE=off go vet
@echo this go plugin builds okay
gocui:
GO111MODULE=off go build -v -x -buildmode=plugin -o gocui.so \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install:
go build -buildmode=plugin -o ~/go/lib/gocui-${VERSION}.so \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
cd ~/go/lib && ln -f -s gocui-${VERSION}.so gocui.so
# for testing custom golang
custom:
# GO111MODULE=off go build -v
GO111MODULE=off go build -v -work -buildmode=blah
clean:
rm -f gocui *.so go.*
rm -f *.pb.go *.patch
go-mod-clean --purge
# Test the README.md & doc.go file
# this runs pkgsite, the binary that does dev.go.dev
@ -27,3 +47,7 @@ redomod:
rm -f go.*
GO111MODULE= go mod init
GO111MODULE= go mod tidy
proto:
autogenpb --proto gocuiView.proto
make goimports

View File

@ -1,3 +1,7 @@
# a console toolkit around gocui
* to build, run 'make build'
* TODO: make a way to trigger plugin builds. 'package plugin' maybe?
[terminals that support ture color](https://github.com/termstandard/colors#truecolor-support-in-output-devices)
[more info about color](https://jvns.ca/blog/2024/10/01/terminal-colours/)

94
add.go
View File

@ -1,94 +0,0 @@
package main
import (
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
var fakeStartWidth int = me.FakeW
var fakeStartHeight int = me.TabH + me.FramePadH
// setup fake labels for non-visible things off screen
func setFake(n *tree.Node) {
var w *guiWidget
w = n.TK.(*guiWidget)
w.isFake = true
w.gocuiSetWH(fakeStartWidth, fakeStartHeight)
fakeStartHeight += w.gocuiSize.Height()
// TODO: use the actual max hight of the terminal window
if fakeStartHeight > 24 {
fakeStartHeight = me.TabH
fakeStartWidth += me.FakeW
}
}
// set the widget start width & height
// func (n *node) addWidget(n *tree.Node) {
func addWidget(n *tree.Node) {
var nw *guiWidget
nw = n.TK.(*guiWidget)
log.Log(INFO, "setStartWH() w.id =", n.WidgetId, "n.name", n.String())
switch n.WidgetType {
case widget.Root:
log.Log(INFO, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.String())
nw.color = &colorRoot
setFake(n)
return
case widget.Flag:
nw.color = &colorFlag
setFake(n)
return
case widget.Window:
nw.frame = false
// nw.color = &colorWindow
nw.setColor(&colorWindow)
wRoot := me.treeRoot.TK.(*guiWidget)
wRoot.redoWindows(0, 0)
// TODO: record the first window here?
// do initial setup of helper widgets here:
if me.dropdownV == nil {
me.dropdownV = makeDropdownView("addWidget() ddview")
}
return
case widget.Tab:
nw.color = &colorTab
// redoWindows(0,0)
return
case widget.Button:
nw.color = &colorButton
case widget.Checkbox:
nw.color = &colorCheckbox
case widget.Dropdown:
nw.color = &colorDropdown
case widget.Combobox:
nw.color = &colorCombobox
case widget.Box:
nw.color = &colorBox
nw.isFake = true
setFake(n)
return
case widget.Grid:
nw.color = &colorGrid
nw.isFake = true
setFake(n)
return
case widget.Group:
nw.color = &colorGroup
nw.frame = false
return
case widget.Label:
nw.color = &colorLabel
nw.frame = false
return
default:
/*
if n.IsCurrent() {
n.updateCurrent()
}
*/
}
nw.showWidgetPlacement("addWidget()")
}

View File

@ -1,3 +1,6 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
@ -6,48 +9,32 @@ import (
"go.wit.com/widget"
)
// this comes from the application
func setChecked(n *tree.Node, b bool) {
if n.WidgetType != widget.Checkbox {
}
n.State.Checked = b
var tk *guiWidget
tk = n.TK.(*guiWidget)
if tk.node.State.Label == "" {
tk.node.State.Label = "BLANK"
tk.setCheckbox()
}
// redraw the checkbox
func (tk *guiWidget) setCheckbox() {
if tk.WidgetType() != widget.Checkbox {
log.Log(WARN, "setCheckbox() being run on widget:", tk.WidgetType())
return
}
if tk.node.State.Checked {
log.Log(WARN, "setCheckbox() got true", tk.node.State.Checked)
tk.labelN = "X " + tk.node.State.Label
if tk.Checked() {
log.Log(WARN, "setCheckbox() got true", tk.Checked())
tk.labelN = "X " + tk.GetLabel()
} else {
log.Log(WARN, "setCheckbox() got false", tk.node.State.Checked)
tk.labelN = " " + tk.node.State.Label
log.Log(WARN, "setCheckbox() got false", tk.Checked())
tk.labelN = "_ " + tk.GetLabel()
}
tk.Hide()
tk.Show()
}
// redraw the checkbox
func (w *guiWidget) setCheckbox() {
if w.node.WidgetType != widget.Checkbox {
log.Log(WARN, "setCheckbox() being run on widget:", w.node.WidgetType)
return
}
if w.node.State.Label == "" {
w.node.State.Label = "BLANK"
}
if w.node.State.Checked {
log.Log(WARN, "setCheckbox() got true", w.node.State.Checked)
w.labelN = "X " + w.node.State.Label
// w.changed = true
} else {
log.Log(WARN, "setCheckbox() got false", w.node.State.Checked)
w.labelN = " " + w.node.State.Label
// w.changed = true
}
// t := len(w.labelN) + 3
// w.gocuiSize.w1 = w.gocuiSize.w0 + t
w.Hide()
w.Show()
}

229
click.go
View File

@ -1,229 +0,0 @@
package main
import (
"errors"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func (w *guiWidget) doWidgetClick() {
switch w.WidgetType {
/*
case widget.Root:
// THIS IS THE BEGINING OF THE LAYOUT
log.Log(GOCUI, "doWidgetClick()", w.String())
wRoot := me.treeRoot.TK.(*guiWidget)
wRoot.redoWindows(0, 0)
case widget.Flag:
log.Log(GOCUI, "doWidgetClick() FLAG widget name =", w.String())
log.Log(GOCUI, "doWidgetClick() if this is the dropdown menu, handle it here?")
*/
case widget.Window:
log.Log(GOCUI, "doWidgetClick() START on window", w.String())
// if the user clicked on the current window, do nothing
/* Ignore this for now and redraw the window anyway
if me.currentWindow == w {
if !w.active {
return
}
}
*/
// if there is a current window, hide it
if me.currentWindow != nil {
me.currentWindow.setColor(&colorWindow)
me.currentWindow.hideWidgets()
me.currentWindow.isCurrent = false
}
// now set this window as the current window
me.currentWindow = w
me.currentWindow.isCurrent = true
// draw the current window
log.Log(GOCUI, "doWidgetClick() set currentWindow to", w.String())
w.setColor(&colorActiveW)
w.DrawAt(3, 2)
w.placeWidgets(3, 2) // compute the sizes & places for each widget
w.active = false
w.showWidgets()
/*
hideFake()
showDebug = true
*/
case widget.Group:
if w.active {
w.active = false
w.placeWidgets(w.startW, w.startH)
w.showWidgets()
} else {
w.active = true
for _, child := range w.children {
child.hideWidgets()
}
}
// w.dumpTree("click end")
case widget.Checkbox:
if w.node.State.Checked {
log.Log(WARN, "checkbox is being set to false")
w.node.State.Checked = false
w.setCheckbox()
} else {
log.Log(WARN, "checkbox is being set to true")
w.node.State.Checked = true
w.setCheckbox()
}
me.myTree.SendUserEvent(w.node)
case widget.Grid:
newR := w.realGocuiSize()
// w,h := n.logicalSize()
// w := newR.w1 - newR.w0
// h := newR.h1 - newR.h0
w.placeGrid(newR.w0, newR.h0)
w.showWidgets()
case widget.Box:
// w.showWidgetPlacement(logNow, "drawTree()")
if w.node.State.Direction == widget.Horizontal {
log.Log(GOCUI, "BOX IS HORIZONTAL", w.String())
} else {
log.Log(GOCUI, "BOX IS VERTICAL", w.String())
}
w.placeWidgets(w.startW, w.startH)
w.toggleTree()
case widget.Button:
// doUserEvent(n)
me.myTree.SendFromUser(w.node)
case widget.Combobox:
log.Log(GOCUI, "do the combobox here")
w.showDropdown()
me.dropdownW = w
case widget.Dropdown:
log.Log(GOCUI, "do the dropdown here")
w.showDropdown()
me.dropdownW = w
default:
}
}
func click(g *gocui.Gui, v *gocui.View) error {
mouseW, mouseH := me.baseGui.MousePosition()
log.Log(GOCUI, "click() START gocui name:", v.Name())
w := findUnderMouse()
// if the dropdown view is visable, process it no matter what
if me.dropdownV.Visible() {
me.dropdownV.dropdownClicked(mouseW, mouseH)
}
if w == me.dropdownV {
return nil
}
if w == nil {
log.Error(errors.New("click() could not find widget for view =" + v.Name()))
} else {
log.Log(GOCUI, "click() Found widget =", w.node.WidgetId, w.String(), ",", w.labelN)
w.doWidgetClick()
}
rootTK := me.treeRoot.TK.(*guiWidget)
realTK := rootTK.findWidgetByView(v)
if realTK == nil {
log.Error(errors.New("toolkit click() out of reality with gocui. v.Name() not in binary tree " + v.Name()))
log.Log(GOCUI, "click() END FAILURE ON gocui v.Name =", v.Name())
// return nil // otherwise gocui exits
}
// double check the widget view really still exists
nameTK := rootTK.findWidgetByName(v.Name())
if nameTK == nil {
log.Error(errors.New("toolkit click() out of reality with gocui. v.Name() not in binary tree " + v.Name()))
return nil
}
if nameTK.v == nil {
log.Log(GOCUI, "click() maybe this widget has had it's view distroyed?", nameTK.cuiName, nameTK.WidgetType)
log.Log(GOCUI, "yep. it's gone now")
return nil
}
// SetCurrentView dies if it's sent an non-existent view
if _, err := g.SetCurrentView(v.Name()); err != nil {
log.Log(GOCUI, "click() END v.Name =", v.Name(), "err =", err)
// return err // return causes gocui.MainLoop() to exit. Do we ever want that to happen here?
return nil
}
log.Log(GOCUI, "click() END gocui name:", v.Name())
return nil
}
func findUnderMouse() *guiWidget {
var widgets []*guiWidget
var f func(w *guiWidget)
w, h := me.baseGui.MousePosition()
// find buttons that are below where the mouse button click
f = func(widget *guiWidget) {
// ignore widgets that are not visible
if widget.Visible() {
if (widget.gocuiSize.w0 <= w) && (w <= widget.gocuiSize.w1) &&
(widget.gocuiSize.h0 <= h) && (h <= widget.gocuiSize.h1) {
widgets = append(widgets, widget)
}
}
for _, child := range widget.children {
f(child)
}
}
rootW := me.treeRoot.TK.(*guiWidget)
f(rootW)
var found *guiWidget
// widgets has everything that matches
for _, w := range widgets {
// prioritize window buttons. This means if some code covers
// up the window widgets, then it will ignore everything else
// and allow the user (hopefully) to redraw or switch windows
// TODO: display the window widgets on top
if w.WidgetType == widget.Window {
return w
}
// w.showWidgetPlacement("findUnderMouse() FOUND")
found = w
}
return found
}
// find the widget under the mouse click
func ctrlDown(g *gocui.Gui, v *gocui.View) error {
var found *guiWidget
// var widgets []*node
// var f func (n *node)
found = findUnderMouse()
if me.ctrlDown == nil {
setupCtrlDownWidget()
var tk *guiWidget
tk = me.ctrlDown.TK.(*guiWidget)
tk.labelN = found.String()
tk.cuiName = "ctrlDown"
// me.ctrlDown.parent = me.rootNode
}
var tk *guiWidget
tk = me.ctrlDown.TK.(*guiWidget)
if found == nil {
found = me.treeRoot.TK.(*guiWidget)
}
tk.labelN = found.String()
newR := found.realGocuiSize()
tk.gocuiSize.w0 = newR.w0
tk.gocuiSize.h0 = newR.h0
tk.gocuiSize.w1 = newR.w1
tk.gocuiSize.h1 = newR.h1
return nil
}

466
color.go
View File

@ -1,181 +1,309 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"math/rand"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
//w.v.SelBgColor = gocui.ColorCyan
//color.go: w.v.SelFgColor = gocui.ColorBlack
//color.go: w.v.BgColor = gocui.ColorGreen
// simple colors for light and dark
type colorT struct {
frame gocui.Attribute
fg gocui.Attribute
bg gocui.Attribute
selFg gocui.Attribute
selBg gocui.Attribute
name string
}
// information about how terminfo works
// https://jvns.ca/blog/2024/10/01/terminal-colours/
var none gocui.Attribute = gocui.AttrNone
var lightPurple gocui.Attribute = gocui.GetColor("#DDDDDD") // light purple
var darkPurple gocui.Attribute = gocui.GetColor("#FFAA55") // Dark Purple
var heavyPurple gocui.Attribute = gocui.GetColor("#88AA55") // heavy purple
var powdererBlue gocui.Attribute = gocui.GetColor("#B0E0E6") // w3c 'powerder blue'
var superLightGrey gocui.Attribute = gocui.GetColor("#55AAFF") // super light grey
// Standard defined colors from gocui:
// ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhite
// v.BgColor = gocui.GetColor("#111111") // crazy red
// v.BgColor = gocui.GetColor("#FF9911") // heavy red
// v.SelBgColor = gocui.GetColor("#FFEE11") // blood red
// v.BgColor = gocui.GetColor("#55AAFF") // super light grey
// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow
// Normal Text On mouseover
//
// Widget Frame Text background Text background
var colorWindow colorT = colorT{
frame: none,
fg: gocui.ColorBlue,
bg: none,
selFg: none,
selBg: powdererBlue,
name: "normal window",
}
var colorActiveW colorT = colorT{none, none, powdererBlue, none, powdererBlue, "active window"}
var colorTab colorT = colorT{gocui.ColorBlue, gocui.ColorBlue, none, none, powdererBlue, "normal tab"}
var colorActiveT colorT = colorT{gocui.ColorBlue, none, powdererBlue, none, powdererBlue, "active tab"}
var colorLabel colorT = colorT{none, none, superLightGrey, none, superLightGrey, "normal label"}
var colorGroup colorT = colorT{none, none, superLightGrey, none, superLightGrey, "normal group"}
var colorDisabled colorT = colorT{
frame: superLightGrey,
fg: superLightGrey,
bg: superLightGrey,
selFg: gocui.ColorBlack,
selBg: gocui.ColorBlack,
name: "disabled widget",
}
var colorButton colorT = colorT{
frame: gocui.ColorGreen,
fg: none,
bg: gocui.ColorWhite,
selFg: gocui.ColorGreen,
selBg: gocui.ColorBlack,
name: "normal button",
}
var colorDropdown colorT = colorT{
frame: gocui.ColorYellow,
fg: none,
bg: gocui.ColorWhite,
selFg: gocui.ColorYellow,
selBg: gocui.ColorBlack,
name: "normal button",
}
var colorCombobox colorT = colorT{
frame: gocui.ColorBlue,
fg: none,
bg: gocui.ColorWhite,
selFg: gocui.ColorBlue,
selBg: gocui.ColorBlack,
name: "normal button",
}
var colorCheckbox colorT = colorT{
frame: gocui.ColorRed,
fg: none,
bg: gocui.ColorWhite,
selFg: gocui.ColorRed,
selBg: gocui.ColorBlack,
name: "normal checkbox",
}
// widget debugging colors. these widgets aren't displayed unless you are debugging
var colorRoot colorT = colorT{gocui.ColorRed, none, powdererBlue, none, gocui.ColorBlue, "debug root"}
var colorFlag colorT = colorT{gocui.ColorRed, none, powdererBlue, none, gocui.ColorGreen, "debug flag"}
var colorBox colorT = colorT{gocui.ColorRed, none, lightPurple, none, gocui.ColorCyan, "debug box"}
var colorGrid colorT = colorT{gocui.ColorRed, none, lightPurple, none, gocui.ColorRed, "debug grid"}
var colorNone colorT = colorT{none, none, none, none, none, "debug none"}
// actually sets the colors for the gocui element
// the user will see the colors change when this runs
// TODO: move all this to a protobuf
// TODO: add black/white only flag for ttyS0
// TODO: or fix kvm/qemu serial console & SIGWINCH.
// TODO: and minicom and uboot and 5 million other things.
// TODO: maybe enough of us could actually do that if we made it a goal.
// TODO: start with riscv boards and fix it universally there
// TODO: so just a small little 'todo' item here
func (tk *guiWidget) setColor(newColor *colorT) {
if tk.color == newColor {
// nothing to do since the colors have nto changed
return
// TODO: fix kvm/qemu serial console & SIGWINCH.
// TODO: check minicom (doesn't work)
// TODO: fix riscv boards
// DONE ON ENABLE() WIDGET
// restores the last saved color and makes it active
func (tk *guiWidget) restoreEnableColor() {
if tk.color == nil {
tk.color = new(colorT)
}
tk.color = newColor
tk.color.frame = tk.colorLast.frame
tk.color.fg = tk.colorLast.fg
tk.color.bg = tk.colorLast.bg
tk.color.selFg = tk.colorLast.selFg
tk.color.selBg = tk.colorLast.selBg
tk.activateColor()
}
// DONE ON DISABLE() WIDGET
// makes the button look disabled
func (tk *guiWidget) setColorDisable() {
if tk.color == nil {
tk.color = new(colorT)
}
// save the current color
tk.color.frame = superLightGrey
tk.color.fg = gocui.ColorBlack
tk.color.bg = superLightGrey
tk.color.selFg = superLightGrey
tk.color.selBg = superLightGrey
tk.activateColor()
}
// sets the current gocui highlight colors
func (tk *guiWidget) activateColor() {
if tk.v == nil {
return
}
tk.v.FrameColor = tk.color.frame
tk.v.FgColor = tk.color.fg
tk.v.BgColor = tk.color.bg
tk.v.SelFgColor = tk.color.selFg
tk.v.SelBgColor = tk.color.selBg
}
// saves the color and makes it active
func (tk *guiWidget) updateColor() {
if tk.v == nil {
return
}
if tk.color != nil {
tk.colorLast.frame = tk.color.frame
tk.colorLast.fg = tk.color.fg
tk.colorLast.bg = tk.color.bg
tk.colorLast.selFg = tk.color.selFg
tk.colorLast.selBg = tk.color.selBg
}
tk.activateColor()
}
// Below are all the colors. TODO: move to protobuf and save in a config file
func (tk *guiWidget) setColorWindowFrame() {
if tk.color == nil {
log.Log(NOW, "Set the node to color = nil")
tk.color = &colorNone
tk.color = new(colorT)
}
log.Log(NOW, "Set the node to color =", tk.color.name)
tk.Show()
}
func (w *guiWidget) disableColor() {
if w.color != &colorDisabled {
w.defaultColor = w.color
}
w.setColor(&colorDisabled)
}
func (w *guiWidget) enableColor() {
w.setColor(w.defaultColor)
}
func (w *guiWidget) setDefaultHighlight() {
if w.v == nil {
log.Log(ERROR, "SetColor() failed on view == nil")
return
}
w.v.SelBgColor = gocui.ColorGreen
w.v.SelFgColor = gocui.ColorBlack
}
func randColor() gocui.Attribute {
colors := []string{"Green", "#FFAA55", "Yellow", "Blue", "Red", "Black", "White"}
i := rand.Intn(len(colors))
log.Log(NOW, "randColor() i =", i)
return gocui.GetColor(colors[i])
}
func (w *guiWidget) redoColor(draw bool) {
if w == nil {
return
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
}
log.Sleep(.05)
w.setDefaultHighlight()
// w.setDefaultWidgetColor()
w.Show()
for _, child := range w.children {
child.redoColor(draw)
}
tk.updateColor()
}
// weird. lots of color problems for me on debian sid using the traditional Andy Herzfield 'gnome'
func (tk *guiWidget) setColorWindowTitleActive() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorWindowTitle() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorBG() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
}
tk.updateColor()
}
func (tk *guiWidget) setColorLabel() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
func (tk *guiWidget) setColorButtonDense() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorBlue
tk.color.selBg = gocui.AttrNone
}
tk.updateColor()
}
func (tk *guiWidget) setColorNotifyIcon() {
if tk.color == nil {
tk.color = new(colorT)
}
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorBlue
tk.color.selBg = gocui.AttrNone
tk.updateColor()
}
func (tk *guiWidget) setColorButton() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorBlack
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorBlue
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorInput() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorYellow
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorYellow
tk.color.selBg = gocui.ColorBlack
} else {
tk.color.frame = gocui.ColorYellow
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorYellow
tk.color.selBg = gocui.ColorBlack
}
tk.updateColor()
}
func (tk *guiWidget) setColorModal() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.ColorRed
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorBlack
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
// what genius figured this out?
func (tk *guiWidget) setColorTextbox() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.ColorRed
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorBlack
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
// just notes down here
// what genius figured this out?
// originally from github.com/dimasma0305/GoFetch
func get_teminal_color_palette() string {
@ -195,3 +323,29 @@ func get_teminal_color_palette() string {
return color1 + " " + color2 + " " + color3 + " " + color4 + " " + color5 + " " + color6 + " " + color7 + " " + color8
}
func randColor() gocui.Attribute {
colors := []string{"Green", "#FFAA55", "Yellow", "Blue", "Red", "Black", "White"}
i := rand.Intn(len(colors))
log.Log(NOW, "randColor() i =", i)
return gocui.GetColor(colors[i])
}
var none gocui.Attribute = gocui.AttrNone
var colorNone colorT = colorT{none, none, none, none, none, "debug none"}
var lightPurple gocui.Attribute = gocui.GetColor("#DDDDDD") // light purple
var darkPurple gocui.Attribute = gocui.GetColor("#FFAA55") // Dark Purple
var heavyPurple gocui.Attribute = gocui.GetColor("#88AA55") // heavy purple
var powdererBlue gocui.Attribute = gocui.GetColor("#B0E0E6") // w3c 'powerder blue'
var superLightGrey gocui.Attribute = gocui.GetColor("#55AAFF") // super light grey
// Standard defined colors from gocui:
// ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhite
// v.BgColor = gocui.GetColor("#111111") // crazy red
// v.BgColor = gocui.GetColor("#FF9911") // heavy red
// v.SelBgColor = gocui.GetColor("#FFEE11") // blood red
// v.BgColor = gocui.GetColor("#55AAFF") // super light grey
// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow

7
color.py Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/python3
def color(num, text):
return f"\033[38;5;{num}m{text}\033[0m"
for i in range(300):
print(color(i, f"number {i:02}"))

View File

@ -1,53 +1,102 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func (w *guiWidget) dumpTree(s string) {
// log.Log(ERROR, "dumpTree w", w.node.WidgetId, w.WidgetType, w.String())
// log.Log(ERROR, "dump w", w.WidgetId(), w.WidgetType, w.String())
if w == nil {
log.Log(ERROR, "dumpTree w.TK == nil", w.node.WidgetId, w.WidgetType, w.String())
log.Log(ERROR, "dump w.TK == nil", w.WidgetId(), w.WidgetType(), w.String())
return
}
w.showWidgetPlacement("dumpTree() " + s)
w.dumpWidget("dumpTree() " + s)
for _, child := range w.children {
child.dumpTree(s)
}
}
func (w *guiWidget) showWidgetPlacement(s string) {
func (w *guiWidget) dumpWindows(s string) {
// log.Log(ERROR, "dump w", w.WidgetId(), w.WidgetType, w.String())
if w == nil {
log.Log(ERROR, "dump w.TK == nil", w.WidgetId(), w.WidgetType(), w.String())
return
}
if w.WidgetType() == widget.Window {
s += fmt.Sprintf(" F(%d,%d)", w.force.w0, w.force.h0)
// can't set this here. doesn't work
// w.full.w0 = w.force.w0
// w.full.h0 = w.force.h0
w.dumpWidget("dumpWindow() " + s)
w.windowFrame.dumpWidget("dumpFrame() " + s)
}
for _, child := range w.children {
child.dumpWindows(s)
}
}
// a standard function to print out information about a widget
func (tk *guiWidget) dumpWidget(s string) {
var s1 string
var pId int
if w.node.Parent == nil {
log.Log(INFO, "showWidgetPlacement() parent == nil", w.node.WidgetId, w.cuiName)
// tk.verifyRect()
if tk.parent == nil {
log.Logf(WARN, "showWidgetPlacement() parent == nil wId=%d cuiName=%s", tk.WidgetId(), tk.cuiName)
pId = 0
} else {
pId = w.node.Parent.WidgetId
pId = tk.parent.WidgetId()
}
s1 = fmt.Sprintf("(wId,pId)=(%2d,%2d) ", w.node.WidgetId, pId)
if w.Visible() {
sizeW, sizeH := w.Size()
s1 += fmt.Sprintf("size=(%2d,%2d)", sizeW, sizeH)
s1 += fmt.Sprintf("gocui=(%2d,%2d,%2d,%2d)",
w.gocuiSize.w0, w.gocuiSize.h0, w.gocuiSize.w1, w.gocuiSize.h1)
s1 = fmt.Sprintf("(wId,pId)=(%4d,%4d) ", tk.WidgetId(), pId)
sizeW, sizeH := tk.Size()
hide := "S"
if tk.Hidden() {
hide = "H"
}
s1 += fmt.Sprintf("size=(%3d,%3d)%s)", sizeW, sizeH, hide)
if tk.Visible() {
s1 += fmt.Sprintf("gocui=(%3d,%3d,%3d,%3d)",
tk.gocuiSize.w0, tk.gocuiSize.h0, tk.gocuiSize.w1, tk.gocuiSize.h1)
} else {
sizeW, sizeH := w.Size()
s1 += fmt.Sprintf("size=(%2d,%2d)", sizeW, sizeH)
s1 += fmt.Sprintf(" ")
s1 += fmt.Sprintf(" %3s %3s %3s %3s ", "", "", "", "")
}
if w.node.Parent != nil {
if w.node.Parent.WidgetType == widget.Grid {
s1 += fmt.Sprintf("At(%2d,%2d) ", w.node.State.AtW, w.node.State.AtH)
s1 += fmt.Sprintf(" full=(%3d,%3d,%3d,%3d)", tk.full.w0, tk.full.h0, tk.full.w1, tk.full.h1)
if tk.parent != nil {
if tk.parent.WidgetType() == widget.Grid {
s1 += fmt.Sprintf("At(%3d,%3d)", tk.GridW(), tk.GridH())
s1 += fmt.Sprintf("(%3d,%3d) ", tk.parent.widths[tk.GridW()], tk.parent.heights[tk.GridH()])
} else {
s1 += fmt.Sprintf(" %3s %3s ", "", "")
s1 += fmt.Sprintf(" %3s %3s ", "", "")
}
} else {
s1 += fmt.Sprintf(" %3s %3s ", "", "")
}
tmp := "." + w.String() + ". " + w.cuiName
if w.node.WidgetType == widget.Box {
tmp = "." + w.node.State.Direction.String() + ". " + w.cuiName
var end string
if tk.WidgetType() == widget.Box {
end = fmt.Sprintf("%-8s %-8s %s %s", tk.WidgetType(), tk.cuiName, tk.Direction().String(), tk.String())
} else {
end = fmt.Sprintf("%-8s %-8s %s", tk.WidgetType(), tk.cuiName, tk.String())
}
log.Log(NOW, s1, s, w.node.WidgetType, ",", tmp) //
log.Log(GOCUI, s1, s, end)
}
func printWidgetTree(g *gocui.Gui, v *gocui.View) error {
me.treeRoot.ListWidgets()
return nil
}
func printWidgetPlacements(g *gocui.Gui, v *gocui.View) error {
w := me.treeRoot.TK.(*guiWidget)
w.dumpTree("MM")
w.dumpWindows("WW")
return nil
}

112
draw.go
View File

@ -1,112 +0,0 @@
package main
import (
"errors"
"fmt"
"strconv"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
)
var toggle bool = true
func (w *guiWidget) DrawAt(offsetW, offsetH int) {
w.setColor(&colorActiveW)
w.placeWidgets(offsetW, offsetH) // compute the sizes & places for each widget
w.active = false
w.showWidgets()
}
func (w *guiWidget) toggleTree() {
if toggle {
w.drawTree(toggle)
toggle = false
} else {
w.hideWidgets()
toggle = true
}
}
// display the widgets in the binary tree
func (w *guiWidget) drawTree(draw bool) {
if w == nil {
return
}
w.showWidgetPlacement("drawTree()")
if draw {
// w.textResize()
w.Show()
} else {
w.Hide()
}
for _, child := range w.children {
child.drawTree(draw)
}
}
// display's the text of the widget in gocui
// deletes the old view if it exists and recreates it
func (w *guiWidget) drawView() {
var err error
log.Log(INFO, "drawView() START", w.WidgetType, w.String())
if me.baseGui == nil {
log.Log(ERROR, "drawView() ERROR: me.baseGui == nil", w)
return
}
if w.cuiName == "" {
log.Log(ERROR, "drawView() w.cuiName was not set for widget", w)
w.cuiName = strconv.Itoa(w.node.WidgetId) + " TK"
}
log.Log(INFO, "drawView() labelN =", w.labelN)
// this deletes the button from gocui
me.baseGui.DeleteView(w.cuiName)
w.v = nil
w.textResize()
a := w.gocuiSize.w0
b := w.gocuiSize.h0
c := w.gocuiSize.w1
d := w.gocuiSize.h1
w.v, err = me.baseGui.SetView(w.cuiName, a, b, c, d, 0)
if err == nil {
w.showWidgetPlacement("drawView()")
log.Log(ERROR, "drawView() internal plugin error err = nil")
return
}
if !errors.Is(err, gocui.ErrUnknownView) {
w.showWidgetPlacement("drawView()")
log.Log(ERROR, "drawView() internal plugin error error.IS()", err)
return
}
// this sets up the keybinding for the name of the window
// does this really need to be done? I think we probably already
// know everything about where all the widgets are so we could bypass
// the gocui package and just handle all the mouse events internally here (?)
// for now, the w.v.Name is a string "1", "2", "3", etc from the widgetId
// set the binding for this gocui view now that it has been created
// gocui handles overlaps of views so it will run on the view that is clicked on
me.baseGui.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click)
// this actually sends the text to display to gocui
w.v.Wrap = true
w.v.Frame = w.frame
w.v.Clear()
fmt.Fprint(w.v, w.labelN)
// if you don't do this here, it will be black & white only
if w.color != nil {
w.v.FrameColor = w.color.frame
w.v.FgColor = w.color.fg
w.v.BgColor = w.color.bg
w.v.SelFgColor = w.color.selFg
w.v.SelBgColor = w.color.selBg
}
log.Log(INFO, "drawView() END")
}

View File

@ -1,156 +1,115 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// simulates a dropdown menu in gocui
import (
"fmt"
"strings"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func makeDropdownView(ddItems string) *guiWidget {
newNode := addDropdown()
tk := newNode.TK.(*guiWidget)
tk.labelN = ddItems
tk.SetText(ddItems)
tk.gocuiSize.w0 = 100
tk.gocuiSize.w1 = 120
tk.gocuiSize.h0 = 15
tk.gocuiSize.h1 = 18
tk.v, _ = me.baseGui.SetView("ddview",
tk.gocuiSize.w0,
tk.gocuiSize.h0,
tk.gocuiSize.w1,
tk.gocuiSize.h1, 0)
if tk.v == nil {
return tk
}
tk.v.Wrap = true
tk.v.Frame = true
tk.v.Clear()
fmt.Fprint(tk.v, ddItems)
tk.Show()
return tk
}
func addDropdown() *tree.Node {
// create a new widget in the binary tree
func makeNewFlagWidget(wId int) *guiWidget {
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = 2222
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.labelN = "DropBox text"
tk.node = n
// copy the data from the action message
tk.node.State.Label = "DropBox"
if tk.node.Parent == nil {
tk.node.Parent = me.treeRoot
}
// set the name used by gocui to the id
tk.cuiName = "-1 dropbox"
tk.cuiName = fmt.Sprintf("%d DR", wId)
tk.color = &colorFlag
tk.setColorInput()
// add this new widget on the binary tree
tk.parent = me.treeRoot.TK.(*guiWidget)
if tk.parent == nil {
panic("addDropdown() didn't get treeRoot guiWidget")
panic("makeNewFlagWidget() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return n
return tk
}
func (tk *guiWidget) showDropdown() {
var ddItems string
if me.dropdown.tk == nil {
// should only happen once
me.dropdown.tk = makeNewFlagWidget(me.dropdown.wId)
me.dropdown.tk.dumpWidget("init() dropdown")
}
if me.dropdown.tk == nil {
log.Log(GOCUI, "showDropdown() Is Broken!")
return
}
// todo: fix this after switching to protobuf
// var items []string
// items = tk.node.State.Strings
//for i, s := range items {
me.dropdown.items = []string{} // zero out whatever was there before
for i, s := range tk.node.Strings() {
log.Log(GOCUI, "showDropdown()", tk.String(), i, s)
ddItems += s + "\n"
me.dropdown.items = append(me.dropdown.items, s)
}
log.Log(GOCUI, "new dropdown items should be set to:", me.dropdown.items)
log.Log(GOCUI, "new dropdown items should be set to:", ddItems)
sizeW, sizeH := tk.Size()
log.Log(GOCUI, "showDropdown() size W,H=", sizeW, sizeH)
startW, startH := tk.Position()
log.Log(GOCUI, "showDropdown() location W,H=", startW, startH)
me.dropdownV.MoveToOffset(startW+3, startH+2)
me.dropdownV.labelN = ddItems
me.dropdownV.Show()
}
log.Log(GOCUI, "showDropdown() SHOWING AT W,H=", startW, startH)
me.dropdown.tk.Hide()
me.dropdown.tk.MoveToOffset(startW+3, startH+2)
me.dropdown.tk.labelN = strings.Join(me.dropdown.items, "\n")
me.dropdown.tk.Show()
me.dropdown.active = true
me.dropdown.callerTK = tk
func hideDDview() error {
w, h := me.baseGui.MousePosition()
log.Log(GOCUI, "hide dropdown menu() view msgMouseDown (w,h) =", w, h)
if me.dropdownV == nil {
return gocui.ErrUnknownView
}
if me.dropdownV.v == nil {
return gocui.ErrUnknownView
}
me.dropdownV.SetVisible(false)
return nil
}
r := me.dropdown.tk.gocuiSize // set the 'full' size so that mouse clicks are sent here
me.dropdown.tk.full.w0 = r.w0
me.dropdown.tk.full.w1 = r.w1
me.dropdown.tk.full.h0 = r.h0
me.dropdown.tk.full.h1 = r.h1
func showDDview() error {
w, h := me.baseGui.MousePosition()
log.Log(GOCUI, "show dropdown menu() view msgMouseDown (w,h) =", w, h)
if me.dropdownV == nil {
return gocui.ErrUnknownView
}
if me.dropdownV.v == nil {
return gocui.ErrUnknownView
}
me.dropdownV.SetVisible(true)
return nil
me.dropdown.tk.dumpWidget("showDropdown()")
}
// if there is a drop down view active, treat it like a dialog box and close it
func (w *guiWidget) dropdownClicked(mouseW, mouseH int) string {
w.Hide()
me.dropdown.active = false
startW, startH := w.Position()
log.Log(GOCUI, "dropdownClicked() start (w,h) =", startW, startH)
log.Log(GOCUI, "dropdownClicked() at (w,h) =", mouseW, mouseH)
// only need height to figure out what line in the dropdown menu the user clicked
_, startH := w.Position()
itemNumber := mouseH - startH
items := strings.Split(w.labelN, "\n")
log.Log(GOCUI, "dropdownClicked() look for item", itemNumber, "len(items) =", len(items))
items := me.dropdown.items
// log.Log(GOCUI, "dropdownClicked() look for item", itemNumber, "len(items) =", len(items))
if itemNumber < 1 {
return ""
}
if len(items) >= itemNumber {
log.Log(GOCUI, "dropdownClicked() found", items[itemNumber-1])
// log.Log(GOCUI, "dropdownClicked() found", items[itemNumber-1])
if items[itemNumber-1] != "" {
if me.dropdownW != nil {
log.Log(GOCUI, "dropdownClicked() send event for", me.dropdownW.cuiName, me.dropdownW.WidgetType)
me.dropdownW.SetText(items[itemNumber-1])
me.dropdownW.node.SetCurrentS(items[itemNumber-1])
me.myTree.SendUserEvent(me.dropdownW.node)
if me.dropdown.tk != nil {
// log.Log(GOCUI, "dropdownClicked() send event for", me.dropdownW.cuiName, me.dropdownW.node.WidgetType)
me.dropdown.callerTK.SetText(items[itemNumber-1])
me.dropdown.callerTK.node.SetCurrentS(items[itemNumber-1])
me.myTree.SendUserEvent(me.dropdown.callerTK.node)
}
}
return items[itemNumber-1]
}
return ""
}
func dropdownUnclicked(mouseX, mouseH int) {
if me.dropdownV == nil {
log.Log(GOCUI, "mouseUp() dropdownV = nil", mouseX, mouseH)
return
}
tk := me.dropdownV
log.Log(GOCUI, "mouseUp() view msgMouseDown (check here for dropdown menu click) (w,h) =", mouseX, mouseH)
log.Log(GOCUI, "mouseUp() ddview is the thing that was clicked", mouseX, mouseH)
log.Log(GOCUI, "mouseUp() find out what the string is here", mouseX, mouseH, tk.gocuiSize.h1)
}

234
eventBindings.go Normal file
View File

@ -0,0 +1,234 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"syscall"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// register how the 'gocui' will work as a GO toolkit plugin
// all applications will use these keys. they are universal.
// tells 'gocui' where to send events
func registerHandlers(g *gocui.Gui) {
defer func() {
if r := recover(); r != nil {
log.Info("EVENT BINDINGS recovered in r", r)
return
}
}()
// mouse handlers
g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown) // normal left mouse down
g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown) // mouse with the ctrl key held down
g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp) // mouse button release
g.SetKeybinding("", gocui.MouseWheelUp, gocui.ModNone, wheelsUp) // mouse button release
g.SetKeybinding("", gocui.MouseWheelDown, gocui.ModNone, wheelsDown) // mouse button release
// Ctrl key handlers
g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, doExit) // CTRL-C : exits the application
g.SetKeybinding("", gocui.KeyCtrlV, gocui.ModNone, doPanic) // CTRL-V : force a panic()
g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, openDebuggger) // CTRL-D : open the (D)ebugger
keyForced, modForced := gocui.MustParse("ctrl+z") // setup ctrl+z
g.SetKeybinding("", keyForced, modForced, handle_ctrl_z) // CTRL-Z :cleverly let's you background gocui (breaks cursor mouse on return)
g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, doEsc) // escape key
// regular keys
g.SetKeybinding("", 'H', gocui.ModNone, theHelp) // 'H' toggles on and off the help menu
g.SetKeybinding("", 'O', gocui.ModNone, theStdout) // 'O' toggle the STDOUT window
g.SetKeybinding("", 'D', gocui.ModNone, theDarkness) // 'D' toggles light/dark mode
g.SetKeybinding("", 'q', gocui.ModNone, doExit) // 'q' exit
g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, tabCycleWindows) // '2' use this to test new ideas
// stdout keys
g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, stdoutPgup) // Pgup scroll up the Stdout buffer
g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, stdoutPgdn) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyHome, gocui.ModNone, stdoutHome) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, stdoutArrowUp) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, stdoutArrowDown) // Pgdn scroll down the Stdout buffer
// debugging
g.SetKeybinding("", '2', gocui.ModNone, theNotsure) // '2' use this to test new ideas
g.SetKeybinding("", 'S', gocui.ModNone, theSuperMouse) // 'S' Super Mouse mode!
g.SetKeybinding("", 'M', gocui.ModNone, printWidgetPlacements) // 'M' list all widgets with positions
g.SetKeybinding("", 'L', gocui.ModNone, printWidgetTree) // 'L' list all widgets in tree view
g.SetKeybinding("", 'f', gocui.ModNone, theFind) // 'f' shows what is under your mouse
g.SetKeybinding("", 'd', gocui.ModNone, theLetterD) // 'd' toggles on and off debugging buttons
g.SetKeybinding("", 'q', gocui.ModNone, quit) // 'q' only exits gocui. plugin stays alive (?)
}
// flips on 'super mouse' mode // this was awesome for debugging gocui. never remove this code.
// while this is turned on, it will print out every widget found under the mouse
func theSuperMouse(g *gocui.Gui, v *gocui.View) error {
if me.supermouse {
log.Log(GOCUI, "supermouse off")
me.supermouse = false
} else {
me.supermouse = true
log.Log(GOCUI, "supermouse on")
}
return nil
}
// use this to test code ideas // put whatever you want here and hit '2' to activate it
func theNotsure(g *gocui.Gui, v *gocui.View) error {
log.Info("got to theNotsure(). now what? dark =", me.dark)
me.refresh = true
log.Info("running VerifyParentId()")
me.treeRoot.VerifyParentId()
/*
if me.debug {
log.Info("debugging off")
me.debug = false
} else {
log.Info("debugging on")
me.debug = true
}
win := findWindowUnderMouse()
if win != nil {
win.dumpWidget("found() win. redrawing window:")
win.makeWindowActive()
}
*/
return nil
}
func theDarkness(g *gocui.Gui, v *gocui.View) error {
if me.dark {
me.dark = false
log.Info("you have seen the light")
} else {
me.dark = true
log.Info("you have entered into darkness")
}
return nil
}
func wheelsUp(g *gocui.Gui, v *gocui.View) error {
// log.Info("private wheels up")
me.stdout.pager -= 2
if me.stdout.pager < 0 {
me.stdout.pager = 0
}
me.stdout.tk.refreshStdout()
return nil
}
func wheelsDown(g *gocui.Gui, v *gocui.View) error {
// log.Info("you've landed")
me.stdout.pager += 2
if me.stdout.pager > len(me.stdout.outputS) {
me.stdout.pager = len(me.stdout.outputS)
}
me.stdout.tk.refreshStdout()
return nil
}
func tabCycleWindows(g *gocui.Gui, v *gocui.View) error {
// log.Info("try to switch windows here")
if len(me.allwin) != len(findWindows()) {
me.allwin = findWindows()
}
tk := findNextWindow()
if tk == nil {
log.Info("findNextWindow() err. returned nil")
return nil
}
tk.makeWindowActive()
return nil
}
func doEsc(g *gocui.Gui, v *gocui.View) error {
log.Info("got escape key")
if me.dropdown.active {
me.dropdown.tk.Hide()
me.dropdown.active = false
log.Info("escaped from dropdown")
}
if me.textbox.active {
me.textbox.tk.Hide()
me.textbox.active = false
log.Info("escaped from textbox")
}
return nil
}
func theShow(g *gocui.Gui, v *gocui.View) error {
var w *guiWidget
w = me.treeRoot.TK.(*guiWidget)
w.showWidgets()
return nil
}
func doExit(g *gocui.Gui, v *gocui.View) error {
standardExit()
return nil
}
func doPanic(g *gocui.Gui, v *gocui.View) error {
log.Log(GOCUI, "do panic() here")
standardClose()
panic("forced panic in gocui")
}
func openDebuggger(g *gocui.Gui, v *gocui.View) error {
me.myTree.SendEnableDebugger()
return nil
}
func theFind(g *gocui.Gui, v *gocui.View) error {
w, h := g.MousePosition()
for _, tk := range findByXY(w, h) {
// tk.v.BgColor = gocui.ColorGreen
tk.dumpWidget("theFind()")
// tk.verifyRect()
}
return nil
}
// is run whenever anyone hits 'd' (in an open space)
func theLetterD(g *gocui.Gui, v *gocui.View) error {
// widgets that don't have physical existance in
// a display toolkit are hidden. In the case
// of gocui, they are set as not 'visible' and put offscreen
// or have the size set to zero
// (hopefully anyway) lots of things with the toolkit
// still don't work
fakeStartWidth = me.FakeW
fakeStartHeight = me.TabH + me.FramePadH
if me.showDebug {
showFake()
me.showDebug = false
} else {
hideFake()
me.showDebug = true
}
return nil
}
func theHelp(g *gocui.Gui, v *gocui.View) error {
if me.showHelp {
log.Info("Show the help!")
showHelp()
} else {
log.Info("Hide the help!")
hideHelp()
}
return nil
}
// todo: find and give credit to the person that I found this patch in their forked repo
// handle ctrl+z
func handle_ctrl_z(g *gocui.Gui, v *gocui.View) error {
gocui.Suspend()
log.Info("got ctrl+z")
syscall.Kill(syscall.Getpid(), syscall.SIGSTOP)
log.Info("got ctrl+z syscall() done")
gocui.Resume()
return nil
}

83
eventBindingsStdout.go Normal file
View File

@ -0,0 +1,83 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
func theStdout(g *gocui.Gui, v *gocui.View) error {
// me.stdout.pager = 0
infos := fmt.Sprintf("pager=%d len(%d) ", me.stdout.pager, len(me.stdout.outputS))
infos += fmt.Sprintf("last(%d,%d)", me.stdout.lastW, me.stdout.lastH)
if me.stdout.outputOnTop {
if me.stdout.outputOffscreen {
me.stdout.outputOffscreen = false
log.Info("stdout moved off screen", infos)
me.stdout.lastW = me.stdout.tk.gocuiSize.w0
me.stdout.lastH = me.stdout.tk.gocuiSize.h0
relocateStdoutOffscreen()
return nil
} else {
me.stdout.outputOffscreen = true
log.Info("stdout moved on screen", infos)
}
// move the stdout window back onscreen
me.stdout.tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
me.stdout.outputOnTop = false
setThingsOnTop()
} else {
me.stdout.outputOnTop = true
setThingsOnTop()
}
return nil
}
func stdoutPgup(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager -= me.stdout.Height() - 2
if me.stdout.pager < 0 {
me.stdout.pager = 0
}
tk := me.stdout.tk
tk.refreshStdout()
return nil
}
func stdoutHome(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager = 0
me.stdout.tk.refreshStdout()
return nil
}
func stdoutArrowUp(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager += 1
me.stdout.tk.refreshStdout()
return nil
}
func stdoutArrowDown(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager -= 1
me.stdout.tk.refreshStdout()
return nil
}
func stdoutPgdn(g *gocui.Gui, v *gocui.View) error {
win := findWindowUnderMouse()
if win != nil {
if win.full.Height() > 50 {
log.Info("paging through really large window pager =", win.window.pager)
win.window.pager += 10
return nil
}
}
me.stdout.pager += 10
tk := me.stdout.tk
tk.refreshStdout()
return nil
}

View File

@ -1,6 +1,5 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
@ -8,7 +7,6 @@ import (
"errors"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
@ -19,29 +17,11 @@ import (
// complicated console handling, it sends events here in a clean way.
// This is equivalent to the linux command xev (apt install x11-utils)
func gocuiEvent(g *gocui.Gui) error {
maxX, maxY := g.Size()
mx, my := g.MousePosition()
log.Verbose("handleEvent() START", maxX, maxY, mx, my, msgMouseDown)
if _, err := g.View("msg"); msgMouseDown && err == nil {
moveMsg(g)
}
if widgetView, _ := g.View("msg"); widgetView == nil {
log.Log(NOW, "handleEvent() create output widget now", maxX, maxY, mx, my)
makeOutputWidget(g, "this is a create before a mouse click")
if me.logStdout != nil {
// setOutput(me.logStdout)
}
} else {
log.Verbose("output widget already exists", maxX, maxY, mx, my)
}
me.ecount += 1
mouseMove(g)
log.Verbose("handleEvent() END ", maxX, maxY, mx, my, msgMouseDown)
return nil
}
func dragOutputWindow() {
}
// turns off the frame on the global window
func setFrame(b bool) {
// TODO: figure out what this might be useful for
@ -53,6 +33,8 @@ func setFrame(b bool) {
v.Frame = b
}
// a test. exits gocui, but the application still runs
// maybe can switch toolkits?
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}

74
eventMouse.go Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
func mouseUp(g *gocui.Gui, v *gocui.View) error {
// useful to debug everything that is being clicked on
/*
for _, tk := range findByXY(w, h) {
tk.dumpWidget("mouseUp()")
}
*/
me.mouse.mouseUp = true
me.mouse.currentDrag = nil
if me.mouse.double && (time.Since(me.mouse.down) < me.mouse.doubletime) {
me.mouse.double = false
doMouseDoubleClick(me.mouse.downW, me.mouse.downH)
return nil
}
me.mouse.double = false
if time.Since(me.mouse.down) < me.mouse.clicktime {
doMouseClick(me.mouse.downW, me.mouse.downH)
}
return nil
}
// this is where you have to figure out what
// widget was underneath so you can active
// the right response for the toolkit user's app
func mouseDown(g *gocui.Gui, v *gocui.View) error {
if me.mouse.mouseUp {
if time.Since(me.mouse.down) < me.mouse.doubletime {
me.mouse.double = true
}
me.mouse.mouseUp = false
me.mouse.down = time.Now()
w, h := g.MousePosition()
me.mouse.downW = w
me.mouse.downH = h
win := findWindowUnderMouse()
if win != nil {
w, h := g.MousePosition()
s := fmt.Sprintf("mouse(%d,%d) ", w, h)
offW := win.full.w1 - w
offH := win.full.h1 - h
s += fmt.Sprintf("corner(%d,%d)", offW, offH)
if (offW < 3) && (offH < 3) {
log.Info("attempting resize on ", s, win.cuiName)
me.mouse.resize = true
// store the stdout corner for computing the drag size
me.stdout.lastW = me.stdout.tk.gocuiSize.w0
me.stdout.lastH = me.stdout.tk.gocuiSize.h0
} else {
// log.Info("mouse down resize off", s)
me.mouse.resize = false
}
win.setAsDragging()
}
}
return nil
}

152
eventMouseClick.go Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func (tk *guiWidget) doButtonClick() {
if tk.IsEnabled() {
tk.dumpWidget("click()") // enable this to debug widget clicks
me.myTree.SendFromUser(tk.node)
} else {
log.Info("button is currently disabled by the application")
// tk.dumpWidget("disabled()") // enable this to debug widget clicks
}
}
// handles a mouse click
func doMouseClick(w int, h int) {
// Flag widgets (dropdown menus, etc) are the highest priority. ALWAYS SEND MOUSE CLICKS THERE FIRST
// handle an open dropdown menu or text entry window first
if me.dropdown.active || me.textbox.active {
// can't drag or do anything when dropdown or textbox are visible
for _, tk := range findByXY(w, h) {
if tk.WidgetId() == me.dropdown.wId {
log.Info("got dropdwon click", w, h, tk.cuiName)
tk.dropdownClicked(w, h)
return
}
if tk.WidgetId() == me.textbox.wId {
log.Info("got textbox click", w, h, tk.cuiName)
textboxClosed()
return
}
}
log.Info("a dropdown or textbox is active. you can't click anywhere else (otherwise hit ESC)", w, h)
return
}
win := findWindowUnderMouse()
if win == nil {
log.Log(INFO, "click() nothing was at:", w, h)
log.Log(INFO, "click() check if", w, h, "is the libnotify icon")
if me.notify.icon.tk != nil && me.notify.icon.tk.gocuiSize.inRect(w, h) {
log.Log(GOCUI, "click() is libnotify.icon!")
if me.notify.icon.active {
log.Info("show notify.icon here")
setNotifyIconText("[X]")
me.notify.icon.active = false
} else {
log.Info("hide notify.icon here")
setNotifyIconText("[ ]")
me.notify.icon.active = true
}
return
}
if me.notify.clock.tk != nil && me.notify.clock.tk.gocuiSize.inRect(w, h) {
log.Log(GOCUI, "click() is the clock!")
if me.showHelp {
log.Info("show help")
showHelp()
} else {
log.Info("hide help")
hideHelp()
}
return
}
return
}
if !win.isWindowActive() {
win.makeWindowActive()
return
} else {
// potentally the user is closing the window
if win.checkWindowClose(w, h) {
return
}
}
// look in this window for widgets
// widgets have priority. send the click here first
for _, tk := range win.findByXYreal(w, h) {
switch tk.WidgetType() {
case widget.Checkbox:
if tk.Checked() {
log.Log(WARN, "checkbox is being set to false")
tk.SetChecked(false)
tk.setCheckbox()
} else {
log.Log(WARN, "checkbox is being set to true")
tk.SetChecked(true)
tk.setCheckbox()
}
me.myTree.SendUserEvent(tk.node)
return
case widget.Button:
tk.doButtonClick()
return
case widget.Combobox:
tk.showDropdown()
return
case widget.Dropdown:
tk.showDropdown()
return
case widget.Textbox:
tk.prepTextbox()
return
default:
// TODO: enable the GUI debugger in gocui
// tk.dumpWidget("undef click()") // enable this to debug widget clicks
}
}
}
// todo: use this?
func ctrlDown(g *gocui.Gui, v *gocui.View) error {
log.Info("todo: clicked with ctrlDown")
return nil
}
func doMouseDoubleClick(w int, h int) {
me.mouse.double = false
// log.Printf("actually a double click (%d,%d)", w, h)
if me.dropdown.active || me.textbox.active {
// can't drag or do anything when dropdown or textbox are visible
log.Info("can't double click. dropdown or textbox is active")
return
}
for _, tk := range findByXY(w, h) {
if tk.WidgetType() == widget.Window {
tk.makeWindowActive()
return
}
if tk.WidgetType() == widget.Stdout {
if me.stdout.outputOnTop {
me.stdout.outputOnTop = false
setThingsOnTop()
} else {
me.stdout.outputOnTop = true
setThingsOnTop()
}
return
}
}
}

142
eventMouseDrag.go Normal file
View File

@ -0,0 +1,142 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// 2025 note by jcarr:
// this is one of the coolest things ever worked with.
// Personally, I've been working on making a gocui GO plugin
// so I can use it as a generalized console GUI toolkit.
//
// Well done everyone that has contributed to this gocui project !!!
// I am in your debt. Happy hacking & peace.
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
// this function uses the mouse position to highlight & unhighlight things
// this is run every time the user moves the mouse over the terminal window
func mouseMove(g *gocui.Gui) {
// this runs while the user moves the mouse. this highlights text
// toggle off all highlight views except for whatever is under the mouse
// START HIGHLIGHTING
for _, view := range g.Views() {
view.Highlight = false
}
w, h := g.MousePosition()
// TODO: try to highlight entire grid rows
if v, err := g.ViewByPosition(w, h); err == nil {
// block anything from highlighting while a dialog box is open
if me.dropdown.active || me.textbox.active {
if me.dropdown.tk != nil && me.dropdown.tk.v == v {
v.Highlight = true
}
if me.textbox.tk != nil && me.textbox.tk.v == v {
v.Highlight = true
}
} else {
v.Highlight = true
}
}
// old hack. create the 'msg' view if it does not yet exist
// TODO: put this somewhere more correct
if widgetView, _ := g.View("msg"); widgetView == nil {
if createStdout(g) {
return
}
return
}
// END HIGHLIGHTING
// Super Mouse Mode. very useful for debugging in the past. also, just fun
if me.supermouse {
w, h := g.MousePosition()
for _, tk := range findByXY(w, h) {
s := fmt.Sprintf("SM (%3d,%3d)", w, h)
tk.dumpWidget(s)
}
}
if me.mouse.mouseUp {
return
}
// EVERYTHING BELOW THIS IS RELATED TO MOUSE DRAGGING
// has the mouse been pressed down long enough to start dragging?
if time.Since(me.mouse.down) < me.mouse.clicktime {
// not dragging
return
}
if me.dropdown.active || me.textbox.active {
// can't drag
return
}
// drag whatever was set to drag
if me.mouse.currentDrag != nil {
// me.mouse.currentDrag.dumpWidget(fmt.Sprintf("MM (%3d,%3d)", w, h))
me.mouse.currentDrag.moveNew()
return
}
log.Info(fmt.Sprintf("gocui gui toolkit plugin error. nothing to drag at (%d,%d)", w, h))
return
}
func (tk *guiWidget) setAsDragging() {
me.mouse.currentDrag = tk
tk.lastW = tk.gocuiSize.w0
tk.lastH = tk.gocuiSize.h0
}
// this is how the window gets dragged around
func (tk *guiWidget) moveNew() {
w, h := me.baseGui.MousePosition()
if tk.WidgetType() == widget.Window {
tk.window.wasDragged = true
// compute the new location based off how far the mouse has moved
// since the mouse button was pressed down
tk.gocuiSize.w0 = tk.lastW + w - me.mouse.downW
tk.gocuiSize.h0 = tk.lastH + h - me.mouse.downH
tk.makeWindowActive()
return
}
/*
if tk.WidgetType() == widget.Flag {
me.baseGui.SetView(tk.cuiName, w-3, h-3, w+20, h+20, 0)
// tk.verifyRect()
s := fmt.Sprintf("move(%dx%d) %s ###", w, h, tk.cuiName)
tk.dumpWidget(s)
return
}
*/
if tk.WidgetType() == widget.Stdout {
if me.mouse.resize {
newW := w - me.stdout.lastW
newH := h - me.stdout.lastH
me.stdout.w = newW
me.stdout.h = newH
// log.Info("Resize true", w, h, newW, newH)
// me.stdout.lastW = w - me.stdout.mouseOffsetW
// me.stdout.lastH = h - me.stdout.mouseOffsetH
tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
} else {
// compute the new location based off how far the mouse has moved
// since the mouse button was pressed down
newW := tk.lastW + w - me.mouse.downW
newH := tk.lastH + h - me.mouse.downH
tk.relocateStdout(newW, newH)
// log.Info("Resize false", w, h, newW, newH)
}
setThingsOnTop() // sets help, Stdout, etc on the top after windows have been redrawn
}
}

View File

@ -1,58 +0,0 @@
package main
import (
"bytes"
"errors"
"io"
)
type FakeFile struct {
reader *bytes.Reader
buffer *bytes.Buffer
offset int64
}
func (f *FakeFile) Read(p []byte) (n int, err error) {
n, err = f.reader.ReadAt(p, f.offset)
f.offset += int64(n)
return n, err
}
func (f *FakeFile) Write(p []byte) (n int, err error) {
n, err = f.buffer.Write(p)
f.offset += int64(n)
f.reader.Reset(f.buffer.Bytes())
return n, err
}
func (f *FakeFile) Seek(offset int64, whence int) (int64, error) {
newOffset := f.offset
switch whence {
case io.SeekStart:
newOffset = offset
case io.SeekCurrent:
newOffset += offset
case io.SeekEnd:
newOffset = int64(f.buffer.Len()) + offset
default:
return 0, errors.New("Seek: whence not at start,current or end")
}
// never can get here right?
if newOffset < 0 {
return 0, errors.New("Seek: offset < 0")
}
f.offset = newOffset
return f.offset, nil
}
func NewFakeFile() *FakeFile {
buf := &bytes.Buffer{}
return &FakeFile{
reader: bytes.NewReader(buf.Bytes()),
buffer: buf,
offset: 0,
}
}

231
find.go Normal file
View File

@ -0,0 +1,231 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"slices"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
/*
gocui defines the offset like this:
width -> increases to the right
---------------------------------- hieght
| H = 1 | increases
| | |
| W = 1 W = 18 | |
| | v
| H = 5 | downwards
-------------------------------------
*/
// change over to this name
// returns all the widgets under (X,H) that are visible
func findByXY(w int, h int) []*guiWidget {
rootW := me.treeRoot.TK.(*guiWidget)
// this searches the binary tree recursively (function is right below)
return rootW.findByXYreal(w, h)
}
func (r rectType) inRect(w int, h int) bool {
if (r.w0 <= w) && (w <= r.w1) && (r.h0 <= h) && (h <= r.h1) {
return true
}
return false
}
// this checks a widget to see if it is under (W,H), then checks the widget's children
// anything that matches is passed back as an array of widgets
func (tk *guiWidget) findByXYreal(w int, h int) []*guiWidget {
var widgets []*guiWidget
// if !tk.Visible() {
// ignore widgets that are not visible
// } else {
// check the location to see if this is under (W,H)
// if it is, return this widget
// if (tk.gocuiSize.w0 <= w) && (w <= tk.gocuiSize.w1) &&
// (tk.gocuiSize.h0 <= h) && (h <= tk.gocuiSize.h1) {
// if tk.gocuiSize.inRect(w, h) {
// widgets = append(widgets, tk)
// } else {
// if (tk.full.w0 <= w) && (w <= tk.full.w1) &&
// (tk.full.h0 <= h) && (h <= tk.full.h1) {
if tk.full.inRect(w, h) {
widgets = append(widgets, tk)
}
// log.Log(GOCUI, "findByXY() found", widget.WidgetType(), w, h)
// }
// }
// tk.verifyRect()
// search through the children widgets in the binary tree
for _, child := range tk.children {
widgets = append(widgets, child.findByXYreal(w, h)...)
}
return widgets
}
// returns all the windows from the root of the binary tree
func findWindows() []*guiWidget {
rootW := me.treeRoot.TK.(*guiWidget)
return rootW.findWindows()
}
// walk the binary tree looking for WidgetType == Window
func (tk *guiWidget) findWindows() []*guiWidget {
var found []*guiWidget
if tk.WidgetType() == widget.Window {
found = append(found, tk)
}
for _, child := range tk.children {
found = append(found, child.findWindows()...)
}
return found
}
// used by gocui.TabKey to rotate through the windows
func findNextWindow() *guiWidget {
var found bool
if len(me.allwin) == 0 {
return nil
}
for _, tk := range me.allwin {
if tk.window.active {
found = true
continue
}
if found {
return tk
}
}
// at the end, loop to the beginning
return me.allwin[0]
}
// find the window under the mouse and only the window under the mouse
func findWindowUnderMouse() *guiWidget {
w, h := me.baseGui.MousePosition()
if len(me.allwin) != len(findWindows()) {
me.allwin = findWindows()
}
// if the stdout window is on top, check it first
if me.stdout.outputOnTop {
if me.stdout.tk.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s stdout on top (%dx%d)", me.stdout.tk.cuiName, w, h))
return me.stdout.tk
}
}
/*
// print out the window list
for _, tk := range me.allwin {
log.Info("findWindowUnderMouse() print:", tk.labelN, tk.window.active, tk.window.order)
}
*/
// now check if the active window is below the mouse
for _, tk := range me.allwin {
if tk.window.active {
if tk.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s active window (%dx%d)", tk.cuiName, w, h))
return tk
}
}
}
// well, just find any window then
// sorting by order might work?
slices.SortFunc(me.allwin, func(a, b *guiWidget) int {
return a.window.order - b.window.order
})
/*
// print out the window list
for _, tk := range me.allwin {
log.Info("findWindowUnderMouse() print:", tk.labelN, tk.window.active, tk.window.order)
}
*/
for _, win := range me.allwin {
if win.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s window (%dx%d)", win.cuiName, w, h))
return win
}
}
// okay, no window. maybe the stdout is there?
if me.stdout.tk.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s stdout (%dx%d)", me.stdout.tk.cuiName, w, h))
return me.stdout.tk
}
// geez. nothing! maybe auto return stdout?
log.Info("findWindowUnderMouse() no window found at", w, h)
return nil
}
func (tk *guiWidget) findParentWindow() *guiWidget {
if tk.WidgetType() == widget.Window {
return tk
}
if tk.parent == nil {
return nil
}
return tk.parent.findParentWindow()
}
func (tk *guiWidget) findWidgetByName(name string) *guiWidget {
if tk.cuiName == name {
return tk
}
for _, child := range tk.children {
found := child.findWidgetByName(name)
if found != nil {
return found
}
}
return nil
}
func (tk *guiWidget) findWidgetByView(v *gocui.View) *guiWidget {
if tk.v == v {
return tk
}
if tk.cuiName == v.Name() {
log.Log(NOW, "findWidget() error. names are mismatched or out of sync", tk.cuiName)
log.Log(NOW, "findWidget() or maybe the view has been deleted")
// return tk
}
for _, child := range tk.children {
found := child.findWidgetByView(v)
if found != nil {
return found
}
}
return nil
}
func (tk *guiWidget) findWidgetById(id int) *guiWidget {
if tk.WidgetId() == id {
return tk
}
for _, child := range tk.children {
found := child.findWidgetById(id)
if found != nil {
return found
}
}
return nil
}

80
help.go
View File

@ -1,4 +1,7 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// Prior Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@ -10,32 +13,47 @@ import (
"strings"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
)
var helpText []string = []string{"KEYBINDINGS",
/*
This in helpText doesn't print
"\x1b[0;32m  \x1b[0m", // this was a test to see what might be
// possible with gocui. it doesn't seem to work for me
*/
var helpText []string = []string{"Help Menu",
"",
"?: toggle help",
"d: toggle debugging",
"r: redraw widgets",
"s/h: show/hide all widgets",
"L: list all widgets",
"M: list all widgets positions",
"\x1b[0;32m  \x1b[0m",
"q: quit()",
"p: panic()",
"o: show Stdout",
"l: log to /tmp/witgui.log",
"Ctrl-D: Toggle Debugging",
"Ctrl-V: Toggle Verbose Debugging",
"Ctrl-C: Exit",
"Tab: toggle through windows",
"O: toggle STDOUT",
"H: toggle this gocui menu",
"L: toggle light/dark mode",
"CTRL-c: quit()",
"",
"Debugging:",
"S: Supermouse mode",
"M: list all widget positions",
"L: list all widgets in tree",
}
func hidehelplayout() {
func hideHelp() {
if me.showHelp {
log.Info("help is already down")
me.showHelp = true
return
}
me.showHelp = true
me.baseGui.DeleteView("help")
}
func helplayout() error {
func showHelp() error {
if !me.showHelp {
log.Info("help is already up")
me.showHelp = false
return nil
}
me.showHelp = false
g := me.baseGui
var err error
maxX, _ := g.Size()
@ -47,17 +65,18 @@ func helplayout() error {
}
}
help, err := g.SetView("help", maxX-(newW+me.FramePadW), 0, maxX-1, len(helpText)+me.FramePadH, 0)
a := maxX - (newW + me.FramePadW)
b := me.notify.help.offsetH
c := maxX - 1
d := me.notify.help.offsetH + len(helpText) + me.FramePadH
help, err := g.SetView("help", a, b, c, d, 0)
if err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
return err
}
help.SelBgColor = gocui.ColorGreen
help.SelFgColor = gocui.ColorBlack
// fmt.Fprintln(help, "Enter: Click Button")
// fmt.Fprintln(help, "Tab/Space: Switch Buttons")
// fmt.Fprintln(help, "Backspace: Delete Button")
// fmt.Fprintln(help, "Arrow keys: Move Button")
fmt.Fprintln(help, strings.Join(helpText, "\n"))
@ -65,6 +84,21 @@ func helplayout() error {
return err
}
}
g.SetViewOnTop("help")
me.helpLabel = help
/*
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return nil
} else {
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "made this in showHelp()")
msg := fmt.Sprintf("test to stdout from in showHelp() %d\n", me.ecount)
me.stdout.Write([]byte(msg))
log.Log(NOW, "log.log(NOW) test")
}
}
*/
return nil
}

425
init.go Normal file
View File

@ -0,0 +1,425 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
"time"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// sent via -ldflags
var VERSION string
var BUILDTIME string
var PLUGIN string = "gocui"
// this is called at the very initial connection
// between the app and this gocui plugin
// this is a good place to initialize gocui's default behavior
func toolkitInit() {
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
if me.baseGui == nil {
log.Info("gocui baseGui is still nil")
standardExit()
}
if me.treeRoot == nil {
log.Info("gocui treeRoot is still nil")
standardExit()
}
// w := me.treeRoot.TK.(*guiWidget)
// w.dumpTree("MM")
// w.dumpWindows("WW")
// SETUP HELP START
me.baseGui.Update(testRefresh)
log.Log(INFO, "gocui toolkitInit() trying showHelp() me.ok =", me.ok)
showHelp()
hideHelp()
// SETUP HELP END
// SETUP STDOUT START
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "from setThingsOnTop()")
}
// time.Sleep(300 * time.Millisecond)
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
me.baseGui.Update(testRefresh)
if !me.stdout.init {
log.Log(INFO, "gocui toolkitInit() stdout.Init me.ok =", me.ok)
me.stdout.init = true
relocateStdoutOffscreen()
}
// time.Sleep(1 * time.Second)
me.stdout.outputOnTop = false
setThingsOnTop()
// SETUP STDOUT END
// SETUP BG
if me.BG.tk == nil {
me.BG.tk = makeNewInternalWidget(me.BG.wId)
}
// SETUP libnotify clock and menu
me.notify.clock.once.Do(makeNotifyClock)
me.notify.icon.once.Do(makeNotifyIcon)
// TODO: for some reason, this makes the background doesn't display
// PUT INIT DEBUG COOE HERE
var toggle bool
for i := 0; i < 4; i++ {
// enable this to show early debugging
// w := me.treeRoot.TK.(*guiWidget)
// w.dumpTree("MM")
// w.dumpWindows("WW")
time.Sleep(100 * time.Millisecond)
if toggle {
toggle = false
// log.Info("gocui toolkitInit() put testing true stuff here")
} else {
toggle = true
// log.Info("gocui toolkitInit() put testing false stuff here")
}
setBottomBG()
}
// PUT INIT DEBUG COOE HERE END
// TEST TEXTBOX START
// time.Sleep(1 * time.Second)
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
me.baseGui.Update(testRefresh)
if me.textbox.tk == nil {
log.Log(INFO, "gocui toolkitInit() initTextbox me.ok =", me.ok)
initTextbox()
me.textbox.tk.prepTextbox()
}
// TEST TEXTBOX END
}
func toolkitClose() {
me.baseGui.Close()
}
// a GO GUI plugin should initTree in init()
// this should be done before the application
// starts trying to open up a channel
func init() {
me.myTree = initTree()
}
// sets defaults and establishes communication
// to this toolkit from the wit/gui golang package
func initPlugin() {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(me.outf, "PANIC: initPlugin() recovered %v\n", r)
return
}
}()
var err error
// read in defaults from config protobuf
if val, err := me.myTree.ConfigFind("stdout"); err == nil {
if val == "true" {
me.stdout.startOnscreen = true
// me.stdout.Write([]byte("starting with stdout onscreen\n"))
}
if val == "disable" {
log.Log(INFO, "gocui: attempt to COMPLETELY DISABLE STDOUT LOG")
me.stdout.disable = true
}
}
if val, err := me.myTree.ConfigFind("dark"); err == nil {
if val == "true" {
me.dark = true
}
} else {
// macos iterm2 really only works with dark mode right now
if runtime.GOOS == "macos" {
me.dark = true
}
}
// todo: make this a tmp file that goes away
if !me.stdout.disable {
log.Log(INFO, "stdout.disable == true. writing to /tmp/captureMode.log")
me.outf, err = os.OpenFile("/tmp/captureMode.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Info("error opening file:", err)
os.Exit(0)
}
// todo: some early output still goes to the /tmp/ file
//time.Sleep(200 * time.Millisecond)
log.CaptureMode(me.stdout)
}
me.starttime = time.Now()
log.Log(INFO, "Init() of awesome-gocui")
// init the config struct default values
Set(&me, "default")
// initial app window settings
// initial stdout window settings
me.stdout.w = 180
me.stdout.h = 40
me.stdout.lastW = 4
me.stdout.lastH = 20
// just make up unique values for these
me.dropdown.wId = -77
me.textbox.wId = -55
me.stdout.wId = -4
me.BG.wId = -22
// the clock widget id and offset
me.notify.clock.wId = -5
me.notify.clock.offsetW = 13
me.notify.clock.offsetH = 1
me.notify.icon.wId = -6
me.notify.icon.offsetW = 4
me.notify.icon.offsetH = 1
me.notify.help.wId = -7
me.notify.help.offsetH = 3
Set(&me.dropdown, "default")
// s := fmt.Sprintln("fake default check =", me.FakeW, "dropdown.Id", me.dropdown.Id)
// me.stdout.Write([]byte(s))
me.mouse.mouseUp = true
me.mouse.clicktime = time.Millisecond * 200
me.mouse.doubletime = time.Millisecond * 400
me.newWindowTrigger = make(chan *guiWidget, 1)
go newWindowTrigger()
go refreshGocui()
log.Log(NOW, "Init() start pluginChan")
if me.stdout.disable {
log.Info("Using STDOUT")
} else {
log.Info("Using gocui STDOUT")
os.Stdout = me.outf
log.CaptureMode(me.outf)
}
// init gocui
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
os.Exit(-1)
return
}
me.baseGui = g
g.Cursor = true
g.Mouse = true
// this sets the function that is run on every event. For example:
// When you click the mouse, move the mouse, or press a key on the keyboard
// This is equivalent to xev or similar to cat /dev/input on linux
g.SetManagerFunc(gocuiEvent)
// register how the 'gocui' will work as a GO toolkit plugin
// all applications will use these keys. they are universal.
// registered event handlers still have the events sent to gocuiEvent() above
registerHandlers(g)
time.Sleep(100 * time.Millisecond)
if me.outf != nil {
fmt.Fprintln(me.outf, "hello world", time.Since(me.starttime))
}
// coreStdout()
// createStdout(g)
// tell 'tree' that we are okay to start talking to
me.myTree.InitOK()
me.ok = true // this tells init() it's okay to work with gocui
go gocuiMain()
}
// This goroutine sits in gocui's MainLoop()
func gocuiMain() {
defer func() {
if r := recover(); r != nil {
log.Warn("PANIC ecovered in gocuiMain()", r)
if me.outf == nil {
debug.PrintStack()
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
panic(os.Stdout)
} else {
fmt.Fprintf(me.outf, "PANIC recovered in r = %v", r)
os.Stderr = me.outf
os.Stdout = me.outf
debug.PrintStack()
pprof.Lookup("goroutine").WriteTo(me.outf, 1)
panic(me.outf)
}
}
}()
// me.stdout.Write([]byte("begin gogui.MainLoop()\n"))
if err := me.baseGui.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
log.Log(NOW, "g.MainLoop() panic err =", err)
// normally panic here
panic("gocuiTKmainloop OOPS")
}
}
func standardExit() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
if me.outf != nil {
log.Log(NOW, "standardExit() doing outf.Close()")
me.outf.Close()
}
// log(true, "standardExit() setOutput(os.Stdout)")
// setOutput(os.Stdout)
log.Log(NOW, "standardExit() send back Quit()")
// go sendBackQuit() // don't stall here in case the
// induces a delay in case the callback channel is broken
time.Sleep(200 * time.Millisecond)
log.Log(NOW, "standardExit() exit()")
os.Exit(0)
}
func standardClose() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
me.outf.Close()
// os.Stdin = os.Stdin
// os.Stdout = os.Stdout
// os.Stderr = os.Stderr
log.Log(NOW, "standardExit() send back Quit()")
}
func main() {
}
// this hack is to wait for the application to send something
// before trying to do anything. todo: rethink this someday
func waitOK() {
for {
if me.ok {
return
}
time.Sleep(10 * time.Millisecond)
}
}
// this hack is to wait for the application to send something
// before trying to do anything. todo: rethink this someday
func waitFirstWindow() {
for {
if me.firstWindowOk {
return
}
time.Sleep(10 * time.Millisecond)
}
}
// empty function. this triggers gocui to refresh the screen
func testRefresh(*gocui.Gui) error {
// log.Info("in testRefresh")
return nil
}
// refresh the screen 10 times a second
func refreshGocui() {
defer func() {
if r := recover(); r != nil {
if me.outf == nil {
log.Info("INIT PLUGIN recovered in r", r)
} else {
fmt.Fprintln(me.outf, "INIT PLUGIN recovered in r", r)
}
return
}
}()
var lastRefresh time.Time
lastRefresh = time.Now()
me.refresh = false
for {
time.Sleep(100 * time.Millisecond)
// log.Info("refresh checking ok")
if !me.ok {
continue
}
// redraw the windows if something has changed
if time.Since(lastRefresh) > 1000*time.Millisecond {
if me.refresh {
log.Log(NOW, "newWindowTrigger() sending refresh to channel")
me.newWindowTrigger <- me.treeRoot.TK.(*guiWidget)
me.refresh = false
}
}
// this code updates the clock
if time.Since(lastRefresh) > 1000*time.Millisecond {
// artificially pause clock while dragging.
// this is a reminder to make this refresh code smarter
// after the switch to protocol buffers
me.myTree.Lock()
if me.mouse.mouseUp {
// log.Info("refresh now on mouseUp")
// todo: add logic here to see if the application has changed anything
libNotifyUpdate()
lastRefresh = time.Now()
} else {
if time.Since(lastRefresh) > 3*time.Second {
libNotifyUpdate()
lastRefresh = time.Now()
}
}
me.myTree.Unlock()
}
}
}
// set the widget start width & height
func newWindowTrigger() {
// log.Log(NOW, "newWindowTriggerl() START")
for {
// log.Log(NOW, "GO plugin toolkit made a new window")
select {
case tk := <-me.newWindowTrigger:
// log.Log(NOW, "newWindowTrigger() got new window", tk.cuiName)
// time.Sleep(200 * time.Millisecond)
waitOK()
me.myTree.Lock()
// time.Sleep(200 * time.Millisecond)
redoWindows(me.FirstWindowW, me.FirstWindowH)
me.firstWindowOk = true
if !me.stdout.init {
me.stdout.init = true
relocateStdoutOffscreen()
}
if me.textbox.tk == nil {
initTextbox()
me.textbox.tk.prepTextbox()
}
tk.makeWindowActive()
me.myTree.Unlock()
}
}
}

View File

@ -1,200 +0,0 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"syscall"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// handle ctrl+z
func handle_ctrl_z(g *gocui.Gui, v *gocui.View) error {
gocui.Suspend()
log.Info("got ctrl+z")
syscall.Kill(syscall.Getpid(), syscall.SIGSTOP)
gocui.Resume()
return nil
}
func defaultKeybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
return err
}
// setup ctrl+z
keyForced, modForced := gocui.MustParse("ctrl+z")
if err := g.SetKeybinding("", keyForced, modForced, handle_ctrl_z); err != nil {
log.Error(err)
}
for _, n := range []string{"but1", "but2", "help", "but3"} {
if err := g.SetKeybinding(n, gocui.MouseLeft, gocui.ModNone, showMsg); err != nil {
return err
}
}
if err := g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp); err != nil {
return err
}
// mouseDown() runs whenever you click on an unknown view (?)
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown); err != nil {
return err
}
// if err := g.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click); err != nil {
// return err
// }
/*
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, globalDown); err != nil {
return err
}
*/
if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, msgDown); err != nil {
return err
}
addDebugKeys(g)
return nil
}
func addDebugKeys(g *gocui.Gui) {
// show debugging buttons
g.SetKeybinding("", 'd', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
fakeStartWidth = me.FakeW
fakeStartHeight = me.TabH + me.FramePadH
if showDebug {
showFake()
showDebug = false
} else {
hideFake()
showDebug = true
}
return nil
})
// display the help menu
g.SetKeybinding("", '?', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
if showHelp {
helplayout()
showHelp = false
if me.dropdownV == nil {
me.dropdownV = makeDropdownView("addWidget() ddview")
}
me.dropdownV.Show()
} else {
me.baseGui.DeleteView("help")
showHelp = true
me.dropdownV.Hide()
}
return nil
})
// redraw all the widgets
g.SetKeybinding("", 'r', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
var w *guiWidget
w = me.treeRoot.TK.(*guiWidget)
if redoWidgets {
wRoot := me.treeRoot.TK.(*guiWidget)
wRoot.redoWindows(0, 0)
redoWidgets = false
} else {
w.hideWidgets()
redoWidgets = true
}
return nil
})
// hide all widgets
g.SetKeybinding("", 'h', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
var w *guiWidget
w = me.treeRoot.TK.(*guiWidget)
w.hideWidgets()
return nil
})
// show all widgets
g.SetKeybinding("", 's', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
var w *guiWidget
w = me.treeRoot.TK.(*guiWidget)
w.showWidgets()
return nil
})
// list all widgets
g.SetKeybinding("", 'L', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
me.treeRoot.ListWidgets()
return nil
})
// list all widgets with positions
g.SetKeybinding("", 'M', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
w := me.treeRoot.TK.(*guiWidget)
w.dumpTree("M")
return nil
})
// redo windows
g.SetKeybinding("", 'w', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
wRoot := me.treeRoot.TK.(*guiWidget)
wRoot.redoWindows(0, 0)
return nil
})
// log to output window
g.SetKeybinding("", 'o', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
log.Log(ERROR, "TODO: re-implement this")
/*
if me.logStdout.Visible() {
me.logStdout.SetVisible(false)
// setOutput(os.Stdout)
} else {
me.logStdout.SetVisible(true)
// setOutput(me.logStdout.tk)
}
*/
return nil
})
// exit
g.SetKeybinding("", 'q', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
standardExit()
return nil
})
g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
standardExit()
return nil
})
g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
if showDebug {
me.myTree.SendEnableDebugger()
}
return nil
})
g.SetKeybinding("", gocui.KeyCtrlV, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
return nil
})
// panic
g.SetKeybinding("", 'p', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
standardClose()
panic("forced panic in gocui")
return nil
})
}

292
libnotify.go Normal file
View File

@ -0,0 +1,292 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// this file implements a libnotify-like menu
// also there is SIGWINCH resizing
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
// create a new widget in the binary tree
func makeNewInternalWidget(wId int) *guiWidget {
if me.treeRoot == nil {
log.Info("GOGUI Init ERROR. treeRoot == nil")
return nil
}
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.node = n
tk.node.Parent = me.treeRoot
// set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId)
tk.setColorInput()
// add this new widget on the binary tree
tk.parent = me.treeRoot.TK.(*guiWidget)
if tk.parent == nil {
panic("makeNewFlagWidget() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return tk
}
func makeNotifyClock() {
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return
}
me.notify.clock.tk = makeNewInternalWidget(me.notify.clock.wId)
// me.notify.clock.tk.dumpWidget("init() clock")
me.notify.clock.tk.MoveToOffset(0, 0)
me.notify.clock.tk.labelN = time.Now().Format("15:04:05")
me.notify.clock.tk.frame = false
me.notify.clock.tk.setColorLabel()
me.notify.clock.tk.Show()
me.notify.clock.active = true
// me.notify.clock.tk.dumpWidget("notifyClock()")
}
func makeNotifyIcon() {
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return
}
me.notify.icon.tk = makeNewInternalWidget(me.notify.icon.wId)
// me.notify.icon.tk.dumpWidget("init() menu")
w, _ := me.baseGui.Size()
me.notify.icon.tk.MoveToOffset(w-5, me.notify.icon.offsetH)
me.notify.icon.tk.labelN = "[ ]"
me.notify.icon.tk.frame = false
me.notify.icon.tk.setColorNotifyIcon()
me.notify.icon.tk.Show()
me.notify.icon.active = true
// me.notify.icon.tk.dumpWidget("notifyIcon()")
}
func libNotifyUpdate() {
if me.baseGui == nil {
log.Info("libNotifyUpdate error baseGui == nil")
return
}
// refresh GOCUI
me.baseGui.Update(testRefresh)
// me.baseGui.UpdateAsync(testRefresh) // Async option. probably don't need this?
if me.notify.clock.tk == nil {
log.Info("libNotifyUpdate error clock.tk == nil")
return
}
// check for SIGWINCH. If so, move the libnotify clock
w, h := me.baseGui.Size()
if me.winchW != w || me.winchH != h {
if me.winchW == 0 && me.winchH == 0 {
// this isn't really SIGWINCH. This is the app starting
} else {
log.Printf("gocui: long live SIGWINCH! (w,h) is now (%d,%d)\n", w, h)
}
me.winchW = w
me.winchH = h
me.notify.clock.tk.MoveToOffset(w-me.notify.clock.offsetW, me.notify.clock.offsetH)
me.notify.clock.tk.Hide()
me.notify.clock.tk.Show()
sigWinchBG()
sigWinchIcon()
}
// update the time
me.notify.clock.tk.v.Clear()
me.notify.clock.tk.labelN = time.Now().Format("15:04:05")
me.notify.clock.tk.v.WriteString(me.notify.clock.tk.labelN)
hardDrawAtgocuiSize(me.notify.clock.tk)
// hardDrawUnderMouse(me.notify.clock.tk, "clock")
// log.Info("libNotifyUpdate updated clock", me.notify.clock.tk.labelN)
if me.notify.icon.tk == nil {
log.Info("libNotifyUpdate error menu.tk == nil")
return
}
if me.notify.icon.tk.v == nil {
log.Info("libNotifyUpdate error menu.tk.v == nil")
return
}
// update the menu
hardDrawAtgocuiSize(me.notify.icon.tk)
me.notify.icon.tk.setColorNotifyIcon()
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
me.baseGui.SetViewOnTop(me.notify.clock.tk.v.Name())
}
func setNotifyIconText(s string) {
me.notify.icon.tk.v.Clear()
me.notify.icon.tk.labelN = s
me.notify.icon.tk.v.WriteString(me.notify.icon.tk.labelN)
hardDrawAtgocuiSize(me.notify.icon.tk)
me.notify.icon.tk.setColorNotifyIcon()
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
log.Info("setNotifyIconText() updated menu to:", me.notify.icon.tk.labelN)
}
// in the very end of redrawing things, this will place the help and stdout on the top or botton
// depending on the state the user has chosen
func setThingsOnTop() {
if me.showHelp { // terrible variable name. FIXME
// log.Info("help does not exist")
} else {
me.baseGui.SetViewOnTop("help")
}
if me.notify.clock.tk != nil {
me.baseGui.SetViewOnTop(me.notify.clock.tk.v.Name())
}
if me.notify.icon.tk != nil {
if me.notify.icon.tk.v != nil {
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
}
}
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "from setThingsOnTop()")
}
if me.stdout.tk == nil {
return
}
if me.stdout.tk.v == nil {
return
}
if me.dark {
me.stdout.tk.v.FgColor = gocui.ColorWhite
me.stdout.tk.v.BgColor = gocui.ColorBlack
} else {
me.stdout.tk.v.FgColor = gocui.ColorBlack
me.stdout.tk.v.BgColor = gocui.AttrNone
}
if me.stdout.outputOnTop {
me.baseGui.SetViewOnTop("msg")
} else {
me.baseGui.SetViewOnBottom("msg")
}
if me.stdout.startOnscreen {
// log.Info("THIS TRIGGERS STDOUT") // todo: make a proper init() & move this there
me.stdout.tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
me.stdout.startOnscreen = false
}
setBottomBG()
}
// useful for debuggging
func hardDrawUnderMouse(tk *guiWidget, name string) {
if tk.v != nil {
tk.Hide()
}
w, h := me.baseGui.MousePosition()
a := w
b := h
c := w + 8
d := h + 4
var err error
tk.v, err = me.baseGui.SetView(tk.cuiName, a, b, c, d, 0)
if err == nil {
log.Info("hardDrawUnderMouse() err ok widget", tk.cuiName)
tk.dumpWidget("hard draw err")
}
tk.v.Frame = false
tk.v.Clear()
tk.v.WriteString(tk.labelN + "\n" + name)
}
func hardDrawAtgocuiSize(tk *guiWidget) {
if tk.v != nil {
tk.Hide()
}
a := tk.gocuiSize.w0
b := tk.gocuiSize.h0
c := tk.gocuiSize.w1
d := tk.gocuiSize.h1
var err error
tk.v, err = me.baseGui.SetView(tk.cuiName, a, b, c, d, 0)
if err == nil {
log.Info("hardDrawAtgocuiSize() err ok widget", tk.cuiName)
tk.dumpWidget("hard draw err")
}
tk.v.Frame = false
tk.v.Clear()
tk.v.WriteString(tk.labelN)
log.Verbose("hardDrawAtgocuiSize() err ok widget", tk.cuiName, a, b, c, d, tk.v.Name())
}
func sigWinchIcon() {
w, _ := me.baseGui.Size()
me.notify.icon.tk.MoveToOffset(w-me.notify.icon.offsetW, me.notify.icon.offsetH)
me.notify.icon.tk.Hide()
me.notify.icon.tk.Show()
}
func sigWinchBG() {
tk := me.BG.tk
w, h := me.baseGui.Size()
a := -1
b := -1
c := w + 1
d := h + 1
var err error
tk.v, err = me.baseGui.SetView(tk.cuiName, a, b, c, d, 0)
if err == nil {
if tk.v == nil {
tk.dumpWidget("drawView() err")
log.Log(ERROR, "drawView() internal plugin error err = nil")
}
return
}
log.Log(INFO, "background resized to", a, b, c, d)
}
// find the "BG" widget and set it to the background on the very very bottom
func setBottomBG() {
if me.BG.tk == nil {
log.Info("background tk widget not initialized")
return
}
tk := me.BG.tk
// log.Info("found BG. setting to bottom", tk.cuiName)
if tk.v == nil {
sigWinchBG()
return
}
if me.dark {
tk.v.BgColor = gocui.ColorBlack
} else {
tk.v.BgColor = gocui.ColorWhite
}
tk.v.Clear()
me.baseGui.SetViewOnBottom(tk.cuiName)
w, h := me.baseGui.Size()
me.baseGui.SetView(tk.cuiName, -1, -1, w+1, h+1, 0)
}

7
log.go
View File

@ -1,3 +1,6 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
/*
@ -8,8 +11,6 @@ import (
"go.wit.com/log"
)
var outputS []string
var NOW *log.LogFlag
var INFO *log.LogFlag
var GOCUI *log.LogFlag
@ -23,7 +24,7 @@ func init() {
full := "go.wit.com/gui"
short := "gocui"
GOCUI = log.NewFlag("GOCUI", false, full, short, "gocui internals")
GOCUI = log.NewFlag("GOCUI", true, full, short, "gocui internals")
full = "go.wit.com/toolkits/gocui"
short = "gocui"

192
main.go
View File

@ -1,192 +0,0 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"os"
"runtime/debug"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
)
func queueToolkitClose() {
me.baseGui.Close()
}
func queueSetChecked(n *tree.Node, b bool) {
setChecked(n, b)
}
// sets defaults and establishes communication
// to this toolkit from the wit/gui golang package
func init() {
log.Log(INFO, "Init() of awesome-gocui")
// init the config struct default values
Set(&me, "default")
// Set(&me, "dense")
me.myTree = tree.New()
me.myTree.PluginName = "gocui"
me.myTree.NodeAction = newaction
me.myTree.Add = newAdd
me.myTree.SetTitle = newSetTitle
me.myTree.SetLabel = newSetLabel
me.myTree.SetText = newSetText
me.myTree.AddText = newAddText
me.myTree.SetChecked = queueSetChecked
me.myTree.ToolkitClose = queueToolkitClose
log.Log(NOW, "Init() start pluginChan")
log.Sleep(.1) // probably not needed, but in here for now under development
go mainGogui()
log.Sleep(.1) // probably not needed, but in here for now under development
}
func standardExit() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
outf.Close()
// log(true, "standardExit() setOutput(os.Stdout)")
// setOutput(os.Stdout)
log.Log(NOW, "standardExit() send back Quit()")
// go sendBackQuit() // don't stall here in case the
// induces a delay in case the callback channel is broken
log.Sleep(1)
log.Log(NOW, "standardExit() exit()")
os.Exit(0)
}
func standardClose() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
outf.Close()
os.Stdin = os.Stdin
os.Stdout = os.Stdout
os.Stderr = os.Stderr
log.Log(NOW, "standardExit() send back Quit()")
}
var outf *os.File
func main() {
}
var origStdout *os.File
var origStderr *os.File
func mainGogui() {
defer func() {
if r := recover(); r != nil {
log.Warn("YAHOOOO Recovered in guiMain application:", r)
log.Warn("Recovered from panic:", r)
me.baseGui.Close()
me.myTree.SendToolkitPanic()
// attempts to control STDOUT
/*
// allow gocui to close if possible, then print stack
log.Sleep(1)
me.myTree.SendToolkitLoad("nocui")
log.Sleep(3)
os.Stdout = origStdout
os.Stderr = origStderr
log.Warn("Stack trace:")
debug.PrintStack()
// attempt to switch to the nocui toolkit
log.Sleep(1)
// panic("BUMMER")
*/
return
}
}()
// attempts to control STDOUT
/*
var err error
log.Log(INFO, "main() start Init()")
outf, err = os.OpenFile("/tmp/witgui.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Error(err, "error opening file: %v")
os.Exit(0)
}
origStdout = os.Stdout
os.Stdout = outf
defer outf.Close()
// setOutput(outf)
// log("This is a test log entry")
ferr, _ := os.OpenFile("/tmp/witgui.err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
origStderr = os.Stderr
os.Stderr = ferr
*/
gocuiMain()
}
// This initializes the gocui package
// it runs SetManagerFunc which passes every input
// event (keyboard, mouse, etc) to the function "gocuiEvent()"
func gocuiMain() {
defer func() {
if r := recover(); r != nil {
log.Warn("YAHOOOO Recovered in gocuiMain()", r)
log.Warn("Recovered from panic:", r)
me.baseGui.Close()
// allow gocui to close if possible, then print stack
log.Sleep(1)
os.Stdout = origStdout
os.Stderr = origStderr
me.myTree.SendToolkitPanic()
log.Warn("Stack trace:")
debug.PrintStack()
// panic("BUMMER 2")
// attempt to switch to the nocui toolkit
log.Sleep(1)
me.myTree.SendToolkitLoad("nocui")
log.Sleep(3)
me.myTree.SendToolkitLoad("nocui")
// panic("BUMMER")
return
}
}()
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
return
}
defer g.Close()
me.baseGui = g
g.Cursor = true
g.Mouse = true
// this sets the function that is run on every event. For example:
// When you click the mouse, move the mouse, or press a key on the keyboard
// This is equivalent to xev or similar to cat /dev/input on linux
g.SetManagerFunc(gocuiEvent)
if err := defaultKeybindings(g); err != nil {
// normally panic here
log.Log(NOW, "defaultKeybindings(g) panic err =", err)
panic("gocuiTKdefaultkeybindings OOPS")
}
if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
log.Log(NOW, "g.MainLoop() panic err =", err)
// normally panic here
panic("gocuiTKmainloop OOPS")
}
}

View File

@ -1,84 +0,0 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
)
// this function uses the mouse position to highlight & unhighlight things
// this is run every time the user moves the mouse over the terminal window
func mouseMove(g *gocui.Gui) {
mx, my := g.MousePosition()
for _, view := range g.Views() {
view.Highlight = false
}
if v, err := g.ViewByPosition(mx, my); err == nil {
v.Highlight = true
}
}
func msgDown(g *gocui.Gui, v *gocui.View) error {
initialMouseX, initialMouseY = g.MousePosition()
log.Log(GOCUI, "msgDown() X,Y", initialMouseX, initialMouseY)
if vx, vy, _, _, err := g.ViewPosition("msg"); err == nil {
xOffset = initialMouseX - vx
yOffset = initialMouseY - vy
msgMouseDown = true
}
return nil
}
func mouseUp(g *gocui.Gui, v *gocui.View) error {
w, h := g.MousePosition()
dropdownUnclicked(w, h)
if msgMouseDown {
msgMouseDown = false
if movingMsg {
movingMsg = false
return nil
} else {
g.DeleteView("msg")
}
} else if globalMouseDown {
globalMouseDown = false
g.DeleteView("globalDown")
}
return nil
}
func mouseDown(g *gocui.Gui, v *gocui.View) error {
mx, my := g.MousePosition()
if vx0, vy0, vx1, vy1, err := g.ViewPosition("msg"); err == nil {
if mx >= vx0 && mx <= vx1 && my >= vy0 && my <= vy1 {
return msgDown(g, v)
}
}
globalMouseDown = true
maxX, _ := g.Size()
findUnderMouse()
msg := fmt.Sprintf("mouseDown() Mouse really down at: %d,%d", mx, my)
// dropdownClicked(mx, my)
x := mx - len(msg)/2
if x < 0 {
x = 0
} else if x+len(msg)+1 > maxX-1 {
x = maxX - 1 - len(msg) - 1
}
log.Log(GOCUI, "mouseDown() about to write out message to 'globalDown' view. msg =", msg)
if v, err := g.SetView("globalDown", x, my-1, x+len(msg)+1, my+1, 0); err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
return err
}
v.WriteString(msg)
}
return nil
}

63
node.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"go.wit.com/widget"
)
func (tk *guiWidget) WidgetType() widget.WidgetType {
if tk.node == nil {
return widget.Label
}
return tk.node.WidgetType
}
func (tk *guiWidget) WidgetId() int {
return tk.node.WidgetId
}
func (tk *guiWidget) GetLabel() string {
return tk.node.GetLabel()
}
func (tk *guiWidget) IsEnabled() bool {
return tk.node.IsEnabled()
}
func (tk *guiWidget) Checked() bool {
return tk.node.State.Checked
}
func (tk *guiWidget) Hidden() bool {
if tk.node == nil {
return false
}
if tk.parent == nil {
return tk.node.Hidden()
}
if tk.parent.WidgetId() == 0 {
return tk.node.Hidden()
}
if tk.parent.Hidden() {
return true
}
return tk.node.Hidden()
}
func (tk *guiWidget) Direction() widget.Orientation {
return tk.node.State.Direction
}
func (tk *guiWidget) GridW() int {
return tk.node.State.AtW
}
func (tk *guiWidget) GridH() int {
return tk.node.State.AtH
}
func (tk *guiWidget) SetChecked(b bool) {
tk.node.State.Checked = b
}

208
place.go
View File

@ -1,19 +1,45 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"strings"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
/*
gocui defines the offset like this:
width -> increases to the right
---------------------------------- hieght
| H = 1 | increases
| | |
| W = 1 W = 18 | |
| | v
| H = 5 | downwards
-------------------------------------
*/
// moves the gocui view to the W and H offset on the screen
func (tk *guiWidget) MoveToOffset(W, H int) {
tk.gocuiSetWH(W, H)
}
// returns where the corner of widget starts (upper left)
func (tk *guiWidget) Position() (int, int) {
return tk.gocuiSize.w0, tk.gocuiSize.h0
}
func (w *guiWidget) placeBox(startW int, startH int) {
if w.WidgetType != widget.Box {
if w.WidgetType() != widget.Box {
return
}
// tk.dumpTree("beforebox")
w.full.w0 = startW
w.full.h0 = startH
newW := startW
newH := startH
@ -24,7 +50,7 @@ func (w *guiWidget) placeBox(startW int, startH int) {
// re-get the Size (they should not have changed, but maybe they can?)
// TODO: figure this out or report that they did
sizeW, sizeH = child.Size()
if w.node.State.Direction == widget.Vertical {
if w.Direction() == widget.Vertical {
log.Log(INFO, "BOX IS VERTICAL ", w.String(), "newWH()", newW, newH, "child()", sizeW, sizeH, child.String())
// expand based on the child height
newH += sizeH
@ -35,7 +61,6 @@ func (w *guiWidget) placeBox(startW int, startH int) {
}
log.Log(INFO, "BOX END size(W,H) =", sizeW, sizeH, "new(W,H) =", newW, newH)
}
// tk.dumpTree("afterbox")
}
func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
@ -45,37 +70,57 @@ func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
if me.treeRoot == nil {
return 0, 0
}
if tk.Hidden() {
return 0, 0
}
tk.startW = startW
tk.startH = startH
switch tk.WidgetType {
switch tk.WidgetType() {
case widget.Window:
newW := startW
newH := startH
var maxH int = 0
tk.full.w0 = startW
tk.full.h0 = startH
startW += -2
startH += 3
for _, child := range tk.children {
child.placeWidgets(newW, newH)
sizeW, sizeH := child.Size()
if sizeW < 20 {
sizeW = 20
}
newW += sizeW
if sizeH > maxH {
maxH = sizeH
}
child.placeWidgets(startW, startH)
sizeW, _ := child.Size()
startW += sizeW + 4 // add the width to move the next widget over
}
return newW - startW, maxH
return startW, startH
case widget.Tab:
case widget.Grid:
return tk.placeGrid(startW, startH)
// tk.dumpWidget(fmt.Sprintf("PlaceGridS(%d,%d)", startW, startH))
// if you reset the values here, grid horizontal stacking doesn't work anymore
// tk.widths = make(map[int]int) // how tall each row in the grid is
// tk.heights = make(map[int]int) // how wide each column in the grid is
newW, newH := tk.placeGrid(startW, startH)
tk.full.w0 = newW
tk.full.h0 = newH
tk.full.w1 = newW
tk.full.h1 = newH
// tk.dumpWidget(fmt.Sprintf("PlaceGridE(%d,%d)", newW, newH))
return newW, newH
case widget.Box:
tk.placeBox(startW, startH)
tk.full.w0 = startW
tk.full.h0 = startH
tk.full.w1 = startW
tk.full.h1 = startH
// tk.dumpWidget(fmt.Sprintf("PlaceBox(%d,%d)", startW, startH))
return 0, 0
case widget.Stdout:
tk.setStdoutWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
case widget.Group:
// move the group to the parent's next location
tk.gocuiSetWH(startW, startH)
tk.full.w0 = startW
tk.full.h0 = startH
tk.full.w1 = startW
tk.full.h1 = startH
newW := startW + me.GroupPadW
newH := startH + 1 // normal hight of the group label
@ -94,7 +139,27 @@ func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
}
log.Log(INFO, "REAL HEIGHT sizeW:", sizeW, "sizeH:", sizeH)
}
return maxW, newH - startH
newH = newH - startH
// tk.dumpWidget(fmt.Sprintf("PlaceGroup(%d,%d)", maxW, newH))
return maxW, newH
case widget.Button:
if tk.isWindowDense() && tk.isInGrid() {
tk.frame = false
// tk.color = nil
// tk.defaultColor = nil
/*
if tk.IsEnabled() {
tk.setColorButtonDense()
} else {
tk.setColorDisable()
}
*/
// if tk.full.Height() > 0 {
tk.full.h1 = tk.full.h0
// }
}
tk.gocuiSetWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
default:
tk.gocuiSetWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
@ -102,25 +167,58 @@ func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
return 0, 0
}
func (tk *guiWidget) isWindowDense() bool {
if tk.WidgetType() == widget.Window {
return tk.window.dense
}
if tk.parent == nil {
return true
}
return tk.parent.isWindowDense()
}
func (tk *guiWidget) isInGrid() bool {
if tk.WidgetType() == widget.Grid {
return true
}
if tk.parent == nil {
return true
}
return tk.parent.isInGrid()
}
func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
// w.showWidgetPlacement("grid0:")
if w.WidgetType != widget.Grid {
if w.WidgetType() != widget.Grid {
return 0, 0
}
dense := w.isWindowDense()
w.full.w0 = startW
w.full.h0 = startH
// first compute the max sizes of the rows and columns
for _, child := range w.children {
childW, childH := child.placeWidgets(child.startW, child.startH)
// set the child's realWidth, and grid offset
if w.widths[child.node.State.AtW] < childW {
w.widths[child.node.State.AtW] = childW
if w.widths[child.GridW()] < childW {
w.widths[child.GridW()] = childW
}
if w.heights[child.node.State.AtH] < childH {
w.heights[child.node.State.AtH] = childH
if w.heights[child.GridH()] < childH {
w.heights[child.GridH()] = childH
}
if dense {
if w.heights[child.GridH()] > 0 {
w.heights[child.GridH()] = 1
} else {
w.heights[child.GridH()] = 0
}
}
// child.showWidgetPlacement("grid: ")
log.Log(INFO, "placeGrid:", child.String(), "child()", childW, childH, "At()", child.node.State.AtW, child.node.State.AtH)
log.Log(INFO, "placeGrid:", child.String(), "child()", childW, childH, "At()", child.GridW(), child.GridH())
}
var maxW int = 0
@ -132,12 +230,12 @@ func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
var totalW, totalH int
for i, w := range w.widths {
if i < child.node.State.AtW {
if i < child.GridW() {
totalW += w
}
}
for i, h := range w.heights {
if i < child.node.State.AtH {
if i < child.GridH() {
totalH += h
}
}
@ -153,11 +251,13 @@ func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
maxH = totalH
}
log.Log(INFO, "placeGrid:", child.String(), "new()", newW, newH, "At()", child.node.State.AtW, child.node.State.AtH)
log.Log(INFO, "placeGrid:", child.String(), "new()", newW, newH, "At()", child.GridW(), child.GridH())
child.placeWidgets(newW, newH)
// child.showWidgetPlacement("grid2:")
}
// w.showWidgetPlacement("grid3:")
w.full.w1 = startW + maxW
w.full.h1 = startH + maxH
return maxW, maxH
}
@ -165,9 +265,11 @@ func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
func (w *guiWidget) realGocuiSize() *rectType {
var f func(tk *guiWidget, r *rectType)
newR := new(rectType)
outputW, outputH := w.Size()
// initialize the values to opposite
newR.w0 = 80
newR.h0 = 24
newR.w0 = outputW
newR.h0 = outputH
if me.baseGui != nil {
maxW, maxH := me.baseGui.Size()
newR.w0 = maxW
@ -201,6 +303,7 @@ func (w *guiWidget) realGocuiSize() *rectType {
return newR
}
/*
func textSize(n *tree.Node) (int, int) {
var tk *guiWidget
tk = n.TK.(*guiWidget)
@ -214,35 +317,21 @@ func textSize(n *tree.Node) (int, int) {
}
return width, height
}
// moves the gocui view the W and H offset on the screen
/*
gocui defines the offset like this:
width -> increases to the right
---------------------------------- hieght
| H = 1 | increases
| | |
| W = 1 W = 18 | |
| | v
| H = 5 | downwards
-------------------------------------
*/
// change over to this name
func (tk *guiWidget) MoveToOffset(W, H int) {
tk.gocuiSetWH(W, H)
}
// returns where the corner of widget starts (upper left)
func (tk *guiWidget) Position() (int, int) {
return tk.gocuiSize.w0, tk.gocuiSize.h0
}
func (tk *guiWidget) gocuiSetWH(sizeW, sizeH int) {
w := len(widget.GetString(tk.value))
lines := strings.Split(widget.GetString(tk.value), "\n")
w := len(tk.GetLabel())
lines := strings.Split(tk.GetLabel(), "\n")
h := len(lines)
if tk.Hidden() {
tk.gocuiSize.w0 = 0
tk.gocuiSize.h0 = 0
tk.gocuiSize.w1 = 0
tk.gocuiSize.h1 = 0
return
}
// tk := n.tk
if tk.isFake {
tk.gocuiSize.w0 = sizeW
@ -264,3 +353,10 @@ func (tk *guiWidget) gocuiSetWH(sizeW, sizeH int) {
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + 1
}
}
func (tk *guiWidget) setStdoutWH(sizeW, sizeH int) {
tk.gocuiSize.w0 = sizeW
tk.gocuiSize.h0 = sizeH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + me.stdout.w
tk.gocuiSize.h1 = tk.gocuiSize.h0 + me.stdout.h
}

199
plugin.go
View File

@ -1,8 +1,10 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
// if you include more than just this import
// then your plugin might be doing something un-ideal (just a guess from 2023/02/27)
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
@ -32,20 +34,26 @@ func newAdd(n *tree.Node) {
w = n.TK.(*guiWidget)
}
*/
// w.setColor(&colorDisabled)
w := n.TK.(*guiWidget)
w.Show()
me.refresh = true // testing code to see if refresh can work
}
func newSetTitle(n *tree.Node, s string) {
newSetText(n, s)
// for gocui as a GUI plugin, SetTitle & SetLabel are identical to SetText
func setTitle(n *tree.Node, s string) {
setText(n, s)
me.refresh = true // testing code to see if refresh can work
}
func newSetLabel(n *tree.Node, s string) {
newSetText(n, s)
func setLabel(n *tree.Node, s string) {
setText(n, s)
me.refresh = true // testing code to see if refresh can work
}
func newSetText(n *tree.Node, s string) {
// setText() and addText() are simple. They take the event sent
// to the GO plugin from the application and lookup the plugin structure
// then pass that event to gocui. This is the transfer point
func setText(n *tree.Node, s string) {
if n == nil {
log.Warn("Tree Error: Add() sent n == nil")
return
@ -56,9 +64,10 @@ func newSetText(n *tree.Node, s string) {
}
w := n.TK.(*guiWidget)
w.SetText(s)
me.refresh = true // testing code to see if refresh can work
}
func newAddText(n *tree.Node, s string) {
func addText(n *tree.Node, s string) {
if n == nil {
log.Warn("Tree Error: Add() sent n == nil")
return
@ -69,51 +78,7 @@ func newAddText(n *tree.Node, s string) {
}
w := n.TK.(*guiWidget)
w.AddText(s)
}
func newaction(n *tree.Node, atype widget.ActionType) {
log.Log(INFO, "newaction() START", atype)
if n == nil {
log.Warn("Tree Error: Add() sent n == nil")
return
}
if n.TK == nil {
log.Warn("Tree sent an action on a widget we didn't seem to have.")
// do this init here again? Probably something
// went wrong and we should reset the our while gocui.View tree
n.TK = initWidget(n)
}
w := n.TK.(*guiWidget)
switch atype {
case widget.Show:
w.Show()
case widget.Hide:
w.Hide()
case widget.Move:
log.Log(NOW, "attempt to move() =", atype, n.WidgetType, n.ProgName())
case widget.ToolkitClose:
log.Log(NOW, "attempting to close the plugin and release stdout and stderr")
standardClose()
case widget.Enable:
w.enable = true
w.enableColor()
case widget.Disable:
w.enable = false
w.disableColor()
case widget.Delete:
if w == nil {
return
} else {
w.hideWidgets()
w.deleteNode()
}
n.DeleteNode()
wRoot := me.treeRoot.TK.(*guiWidget)
wRoot.redoWindows(0, 0)
default:
log.Log(ERROR, "newaction() UNHANDLED Action Type =", atype, "WidgetType =", n.WidgetType, "Name =", n.ProgName())
}
log.Log(INFO, "newaction() END", atype, n.String())
me.refresh = true // testing code to see if refresh can work
}
func (w *guiWidget) deleteGocuiViews() {
@ -151,45 +116,131 @@ func (w *guiWidget) AddText(text string) {
}
w.vals = append(w.vals, text)
for i, s := range w.vals {
log.Log(NOW, "AddText()", w.String(), i, s)
log.Log(INFO, "AddText()", w.String(), i, s)
}
w.SetText(text)
}
func (w *guiWidget) SetText(text string) {
func (tk *guiWidget) SetText(text string) {
var changed bool = false
if w == nil {
if tk == nil {
log.Log(NOW, "widget is nil")
return
}
if w.labelN != text {
w.labelN = text
if tk.labelN != text {
tk.labelN = text
changed = true
}
tk.node.State.Label = text
if !changed {
return
}
if w.Visible() {
w.textResize()
w.Hide()
w.Show()
if tk.Visible() {
tk.textResize()
tk.Hide()
tk.Show()
}
}
func (w *guiWidget) Set(val any) {
if w == nil {
log.Log(WARN, "Set() w == nil. val =", val)
func (tk *guiWidget) GetText() string {
if tk == nil {
log.Log(NOW, "widget is nil")
return ""
}
// deprecate this
if tk.labelN != "" {
return tk.labelN
}
if tk.node == nil {
// return gocui.view name?
return tk.cuiName
}
if tk.GetLabel() != "" {
return tk.GetLabel()
}
return ""
}
func hideDisable() {
if me.textbox.tk == nil {
initTextbox()
me.textbox.tk.prepTextbox()
}
me.textbox.tk.Hide()
me.textbox.tk.enable = false
me.textbox.tk.node.State.Enable = false
me.textbox.active = false
me.baseGui.SetCurrentView("help")
// me.baseGui.DeleteView(me.textbox.tk.cuiName)
// me.baseGui.DeleteView(me.textbox.tk.v.Name())
}
func showDisable() {
if me.textbox.tk == nil {
initTextbox()
me.textbox.tk.prepTextbox()
}
r := new(rectType)
r.w0 = 2
r.h0 = 1
r.w1 = r.w0 + 24
r.h1 = r.h0 + 2
me.textbox.tk.forceSizes(r)
me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this
// log.Info("textbox should be shown")
// showTextbox("Running...")
// me.textbox.tk.dumpWidget("shown?")
me.textbox.tk.setColorModal()
me.textbox.tk.v.Clear()
me.textbox.tk.v.WriteString("Running...")
me.textbox.tk.v.Editable = true
me.textbox.tk.v.Wrap = true
me.baseGui.SetView(me.textbox.tk.cuiName, r.w0, r.h0, r.w1, r.h1, 0)
me.baseGui.SetCurrentView(me.textbox.tk.v.Name())
// bind the enter key to a function so we can close the textbox
me.baseGui.SetKeybinding(me.textbox.tk.v.Name(), gocui.KeyEnter, gocui.ModNone, theCloseTheTextbox)
me.textbox.active = true
}
func (tk *guiWidget) Disable() {
if tk == nil {
log.Log(NOW, "widget is nil")
return
}
log.Log(INFO, "Set() value =", val)
w.value = val.(string)
if w.node.WidgetType == widget.Checkbox {
w.node.State.Checked = widget.GetBool(val)
w.setCheckbox()
}
if w.node.WidgetType == widget.Label {
w.labelN = widget.GetString(val)
switch tk.WidgetType() {
case widget.Box:
showDisable()
return
case widget.Button:
tk.setColorDisable()
return
default:
tk.dumpWidget("fixme: disable")
}
}
func (tk *guiWidget) Enable() {
if tk == nil {
log.Log(NOW, "widget is nil")
return
}
switch tk.WidgetType() {
case widget.Box:
hideDisable()
return
case widget.Button:
tk.restoreEnableColor()
return
default:
tk.dumpWidget("fixme: enable")
}
}

View File

@ -1,117 +0,0 @@
package main
import (
"errors"
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
var outputW int = 180
var outputH int = 24
func moveMsg(g *gocui.Gui) {
mx, my := g.MousePosition()
if !movingMsg && (mx != initialMouseX || my != initialMouseY) {
movingMsg = true
}
g.SetView("msg", mx-xOffset, my-yOffset, mx-xOffset+outputW, my-yOffset+outputH+me.FramePadH, 0)
me.startOutputW = mx - xOffset
me.startOutputH = my - yOffset
g.SetViewOnBottom("msg")
}
func showMsg(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
log.Log(NOW, "showMsg() v.name =", v.Name())
if _, err := g.SetCurrentView(v.Name()); err != nil {
return err
}
_, cy := v.Cursor()
if l, err = v.Line(cy); err != nil {
l = ""
}
makeOutputWidget(g, l)
return nil
}
func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View {
maxX, maxY := g.Size()
if me.treeRoot == nil {
// keep skipping this until the binary tree is initialized
return nil
}
if me.logStdout == nil {
a := new(widget.Action)
a.ProgName = "stdout"
a.WidgetType = widget.Stdout
a.WidgetId = -3
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
n.TK = initWidget(n)
me.logStdout = n
var tk *guiWidget
tk = me.logStdout.TK.(*guiWidget)
// tk.gocuiSize.w0 = maxX - 32
// tk.gocuiSize.h0 = maxY / 2
tk.gocuiSize.w0 = me.startOutputW
tk.gocuiSize.h0 = me.startOutputH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + outputW
tk.gocuiSize.h1 = tk.gocuiSize.h0 + outputH
}
v, err := g.View("msg")
if v == nil {
log.Log(NOW, "makeoutputwindow() this is supposed to happen. v == nil", err)
} else {
log.Log(NOW, "makeoutputwindow() msg != nil. WTF now? err =", err)
}
// help, err := g.SetView("help", maxX-32, 0, maxX-1, 13, 0)
// v, err = g.SetView("msg", 3, 3, 30, 30, 0)
if me.startOutputW == 0 {
me.startOutputW = maxX - 32
}
if me.startOutputW == 0 {
me.startOutputH = maxY / 2
}
// v, err = g.SetView("msg", maxX-32, maxY/2, maxX/2+outputW, maxY/2+outputH, 0)
v, err = g.SetView("msg", me.startOutputW, me.startOutputH, maxX/2+outputW, maxY/2+outputH, 0)
if errors.Is(err, gocui.ErrUnknownView) {
log.Log(NOW, "makeoutputwindow() this is supposed to happen?", err)
}
if err != nil {
log.Log(NOW, "makeoutputwindow() create output window failed", err)
return nil
}
if v == nil {
log.Log(NOW, "makeoutputwindow() msg == nil. WTF now? err =", err)
return nil
} else {
var tk *guiWidget
tk = me.logStdout.TK.(*guiWidget)
tk.v = v
}
v.Clear()
v.SelBgColor = gocui.ColorCyan
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "figure out how to capture STDOUT to here\n"+stringFromMouseClick)
g.SetViewOnBottom("msg")
// g.SetViewOnBottom(v.Name())
return v
}

310
size.go
View File

@ -1,6 +1,11 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"go.wit.com/widget"
)
@ -13,16 +18,16 @@ func (tk *guiWidget) Size() (int, int) {
}
// don't count hidden widgets in size calculations
if tk.node.Hidden() {
if tk.Hidden() {
return 0, 0
}
switch tk.WidgetType {
switch tk.WidgetType() {
case widget.Window:
var maxH int = 0
var maxW int = 0
for _, child := range tk.children {
if tk.node.Hidden() {
if tk.Hidden() {
continue
}
sizeW, sizeH := child.Size()
@ -43,7 +48,7 @@ func (tk *guiWidget) Size() (int, int) {
maxH := tk.gocuiSize.Height()
for _, child := range tk.children {
if tk.node.Hidden() {
if tk.Hidden() {
continue
}
sizeW, sizeH := child.Size()
@ -57,8 +62,15 @@ func (tk *guiWidget) Size() (int, int) {
return maxW + me.GroupPadW + 3, maxH
case widget.Label:
return len(tk.String()) + 2, 1
case widget.Textbox:
return len(tk.String()) + 10, 3 // TODO: compute this based on 'window dense'
case widget.Checkbox:
return len(tk.String()) + 2, 3
return len(tk.String()) + 2, 3 // TODO: compute this based on 'window dense'
case widget.Button:
if tk.isWindowDense() {
return len(tk.String()) + 2, 0
}
return len(tk.String()) + 2, 3 // TODO: compute this based on 'window dense'
}
if tk.isFake {
return 0, 0
@ -67,25 +79,23 @@ func (tk *guiWidget) Size() (int, int) {
}
func (w *guiWidget) sizeGrid() (int, int) {
if w.node.Hidden() {
if w.Hidden() {
return 0, 0
}
// first compute the max sizes of the rows and columns
for _, child := range w.children {
if w.node.Hidden() {
if w.Hidden() {
continue
}
sizeW, sizeH := child.Size()
sizeW += 2
// set the child's realWidth, and grid offset
if w.widths[child.node.State.AtW] < sizeW {
w.widths[child.node.State.AtW] = sizeW
if w.widths[child.GridW()] < sizeW {
w.widths[child.GridW()] = sizeW
}
if w.heights[child.node.State.AtH] < sizeH {
w.heights[child.node.State.AtH] = sizeH
if w.heights[child.GridH()] < sizeH {
w.heights[child.GridH()] = sizeH
}
}
@ -102,21 +112,21 @@ func (w *guiWidget) sizeGrid() (int, int) {
}
func (w *guiWidget) sizeBox() (int, int) {
if w.WidgetType != widget.Box {
if w.WidgetType() != widget.Box {
return 0, 0
}
if w.node.Hidden() {
if w.Hidden() {
return 0, 0
}
var maxW int = 0
var maxH int = 0
for _, child := range w.children {
if w.node.Hidden() {
if w.Hidden() {
continue
}
sizeW, sizeH := child.Size()
if child.node.State.Direction == widget.Vertical {
if child.Direction() == widget.Vertical {
maxW += sizeW
if sizeH > maxH {
maxH = sizeH
@ -130,3 +140,269 @@ func (w *guiWidget) sizeBox() (int, int) {
}
return maxW + me.BoxPadW, maxH
}
/*
var wtf bool
func (tk *guiWidget) verifyRect() bool {
if !tk.Visible() {
// log.Info("verifyRect() tk is not visible", tk.cuiName)
return false
}
vw0, vh0, vw1, vh1, err := me.baseGui.ViewPosition(tk.cuiName)
if err != nil {
// log.Printf("verifyRect() gocui err=%v cuiName=%s v.Name=%s", err, tk.cuiName, tk.v.Name())
vw0, vh0, vw1, vh1, err = me.baseGui.ViewPosition(tk.v.Name())
if err != nil {
log.Printf("verifyRect() ACTUAL FAIL gocui err=%v cuiName=%s v.Name=%s", err, tk.cuiName, tk.v.Name())
return false
}
// return false
}
var ok bool = true
if vw0 != tk.full.w0 {
// log.Info("verifyRect() FIXING w0", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.w0, "w0 =", vw0)
tk.full.w0 = vw0
ok = false
}
if vw1 != tk.full.w1 {
// log.Info("verifyRect() FIXING w1", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.w1, "w1 =", vw1)
tk.full.w1 = vw1
ok = false
}
if vh0 != tk.full.h0 {
// log.Info("verifyRect() FIXING h0", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.h0)
tk.full.h0 = vh0
ok = false
}
if vh1 != tk.full.h1 {
// log.Info("verifyRect() FIXING h1", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.h1)
tk.full.h1 = vh1
ok = false
}
if !ok {
// log.Info("verifyRect() NEED TO FIX RECT HERE", tk.cuiName)
// tk.dumpWidget("verifyRect() FIXME")
}
// log.Printf("verifyRect() OK cuiName=%s v.Name=%s", tk.cuiName, tk.v.Name())
return true
}
*/
func (tk *guiWidget) setFullSize() bool {
r := tk.getFullSize()
if tk.Hidden() {
p := tk.parent
if p != nil {
// tk.full.w0 = p.full.w0
// tk.full.w1 = p.full.w1
// tk.full.h0 = p.full.h0
// tk.full.h1 = p.full.h1
tk.full.w0 = 0
tk.full.w1 = 0
tk.full.h0 = 0
tk.full.h1 = 0
} else {
tk.full.w0 = 0
tk.full.w1 = 0
tk.full.h0 = 0
tk.full.h1 = 0
}
return false
}
var changed bool
if tk.full.w0 != r.w0 {
tk.full.w0 = r.w0
changed = true
}
// widget might be forced to a certain location
if tk.full.w0 < tk.force.w0 {
tk.gocuiSize.w0 = tk.force.w0
tk.full.w0 = tk.force.w0
changed = false
}
if tk.full.w1 != r.w1 {
tk.full.w1 = r.w1
changed = true
}
if tk.full.h0 != r.h0 {
tk.full.h0 = r.h0
changed = true
}
// widget might be forced to a certain location
if tk.full.h0 < tk.force.h0 {
tk.gocuiSize.h0 = tk.force.h0
tk.full.h0 = tk.force.h0
changed = false
}
if tk.full.h1 != r.h1 {
tk.full.h1 = r.h1
changed = true
}
if tk.WidgetType() == widget.Button {
tk.full.h1 = tk.full.h0 + 1
}
if tk.isWindowDense() && tk.isInGrid() {
tk.full.h1 = tk.full.h0
}
if changed {
tk.dumpWidget(fmt.Sprintf("setFullSize(changed)"))
}
return changed
}
func (tk *guiWidget) gridFullSize() rectType {
var r rectType
var first bool = true
for _, child := range tk.children {
cr := child.getFullSize()
if cr.Width() == 0 && cr.Height() == 0 {
// ignore widgets of zero size?
continue
}
if first {
// use the lowest width and hight from children widgets
r.w0 = cr.w0
r.h0 = cr.h0
r.w1 = cr.w1
r.h1 = cr.h1
first = false
// child.dumpWidget(fmt.Sprintf("grid(f)"))
continue
}
// child.dumpWidget(fmt.Sprintf("grid()"))
// use the lowest width and hight from children widgets
if r.w0 > cr.w0 {
r.w0 = cr.w0
}
if r.h0 > cr.h0 {
r.h0 = cr.h0
}
// use the highest width and hight from children widgets
if r.w1 < cr.w1 {
r.w1 = cr.w1
}
if r.h1 < cr.h1 {
r.h1 = cr.h1
}
}
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
return r
}
func (tk *guiWidget) buttonFullSize() rectType {
var r rectType
r.w0 = tk.gocuiSize.w0
r.w1 = tk.gocuiSize.w1
r.h0 = tk.gocuiSize.h0
r.h1 = tk.gocuiSize.h1
// try setting the full values here ? is this right?
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
// total hack. fix this somewhere eventually correctly
if tk.isWindowDense() { // total hack. fix this somewhere eventually correctly
tk.full.h0 += 1 // total hack. fix this somewhere eventually correctly
tk.full.h1 = tk.full.h0 // total hack. fix this somewhere eventually correctly
}
return r
}
// this checks a widget to see if it is under (W,H), then checks the widget's children
// anything that matches is passed back as an array of widgets
func (tk *guiWidget) getFullSize() rectType {
var r rectType
if tk.Hidden() {
/*
p := tk.parent
if p != nil {
return p.full
}
*/
var r rectType
r.w0 = 0
r.w1 = 0
r.h0 = 0
r.h1 = 0
return r
}
if tk.WidgetType() == widget.Grid {
return tk.gridFullSize()
}
// these are 'simple' widgets
// the full size is exactly what gocui uses
switch tk.WidgetType() {
case widget.Label:
r := tk.buttonFullSize()
r.w1 += 5
return r
case widget.Button:
r := tk.buttonFullSize()
r.w1 += 5
return r
case widget.Checkbox:
return tk.buttonFullSize()
case widget.Dropdown:
r := tk.buttonFullSize()
r.w1 += 7 // TODO: fix this to be real
return r
default:
}
if tk.v == nil {
r.w0 = tk.full.w0
r.w1 = tk.full.w1
r.h0 = tk.full.h0
r.h1 = tk.full.h1
} else {
r.w0 = tk.gocuiSize.w0
r.w1 = tk.gocuiSize.w1
r.h0 = tk.gocuiSize.h0
r.h1 = tk.gocuiSize.h1
}
// search through the children widgets in the binary tree
for _, child := range tk.children {
cr := child.getFullSize()
/* this didn't make things work either
if child.v == nil {
continue
}
*/
// use the lowest width and hight from children widgets
if r.w0 > cr.w0 {
r.w0 = cr.w0
}
if r.h0 > cr.h0 {
r.h0 = cr.h0
}
// use the highest width and hight from children widgets
if r.w1 < cr.w1 {
r.w1 = cr.w1
}
if r.h1 < cr.h1 {
r.h1 = cr.h1
}
}
// try setting the full values here ? is this right?
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
return r
}

227
stdoutShow.go Normal file
View File

@ -0,0 +1,227 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"errors"
"fmt"
"slices"
"strings"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/widget"
)
func createStdout(g *gocui.Gui) bool {
if me.stdout.tk == nil {
makeOutputWidget(g, "this is a create before a mouse click")
// me.logStdout.v.Write([]byte(msg))
// this will show very early debugging output
// keep this code commented out but do not remove it. when it doubt, this will be the Light of Elendil
// NEVER REMOVE THIS CODE
msg := fmt.Sprintf("test out gocuiEvent() %d\n", me.ecount)
me.stdout.tk.Write([]byte(msg))
log.Log(NOW, "logStdout test out")
}
return true
}
func coreStdout() {
if me.stdout.tk != nil {
return
}
a := new(widget.Action)
a.ProgName = "2stdout2"
a.WidgetType = widget.Stdout
a.WidgetId = me.stdout.wId
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.stdout.tk = initWidget(n)
tk := me.stdout.tk
tk.gocuiSize.w0 = me.stdout.lastW
tk.gocuiSize.h0 = me.stdout.lastH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + me.stdout.w
tk.gocuiSize.h1 = tk.gocuiSize.h0 + me.stdout.h
}
func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View {
if me.treeRoot == nil {
// keep skipping this until the binary tree is initialized
return nil
}
coreStdout()
if me.stdout.tk == nil {
return nil
}
v, err := g.View("msg")
if v == nil {
// log.Log(NOW, "makeoutputwindow() this is supposed to happen. v == nil", err)
} else {
log.Log(NOW, "makeoutputwindow() msg != nil. WTF now? err =", err)
return v
}
rect := me.stdout.tk.gocuiSize
v, err = g.SetView("msg", rect.w0, rect.h0, rect.w1, rect.h1, 0)
if errors.Is(err, gocui.ErrUnknownView) {
// log.Log(NOW, "makeoutputwindow() this is supposed to happen?", err)
}
if err != nil {
if v == nil {
log.Log(NOW, "makeoutputwindow() BAD: v == nil && err =", err)
}
log.Log(NOW, "makeoutputwindow() create output window failed", err)
return nil
}
if v == nil {
log.Log(NOW, "makeoutputwindow() msg == nil. WTF now? err =", err)
return nil
} else {
me.stdout.tk.v = v
}
v.Clear()
v.SelBgColor = gocui.ColorCyan
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "figure out how to capture STDOUT to here\n"+stringFromMouseClick)
// g.SetViewOnBottom("msg")
// setBottomBG()
me.stdout.tk.v = v
me.stdout.tk.DrawAt(me.stdout.lastW, me.stdout.lastH)
relocateStdoutOffscreen()
/*
if me.stdout.outputOffscreen {
me.stdout.tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
} else {
relocateStdoutOffscreen()
}
*/
return v
}
func relocateStdoutOffscreen() {
if me.stdout.tk == nil {
return
}
if !me.stdout.disable {
log.Info("Using gocui STDOUT")
log.CaptureMode(me.stdout.tk)
}
newW := 10
newH := 0 - me.stdout.h - 4
me.stdout.tk.relocateStdout(newW, newH)
}
func (tk *guiWidget) relocateStdout(w int, h int) {
w0 := w
h0 := h
w1 := w + me.stdout.w
h1 := h + me.stdout.h
tk.gocuiSize.w0 = w0
tk.gocuiSize.w1 = w1
tk.gocuiSize.h0 = h0
tk.gocuiSize.h1 = h1
tk.full.w0 = w0
tk.full.w1 = w1
tk.full.h0 = h0
tk.full.h1 = h1
me.baseGui.SetView("msg", w0, h0, w1, h1, 0)
// me.baseGui.SetViewOnBottom("msg")
}
// from the gocui devs:
// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (w stdout) Height() int {
if w.tk == nil {
return 40
}
return w.tk.gocuiSize.Height() - 2
}
func (w stdout) Write(p []byte) (n int, err error) {
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
lines := strings.Split(strings.TrimSpace(string(p)), "\n")
me.stdout.outputS = append(me.stdout.outputS, lines...)
if me.outf != nil {
fmt.Fprint(me.outf, string(p))
}
return len(p), nil
}
func (w *guiWidget) Write(p []byte) (n int, err error) {
lines := strings.Split(strings.TrimSpace(string(p)), "\n")
if me.outf != nil {
fmt.Fprint(me.outf, string(p))
}
if w == nil {
me.stdout.outputS = append(me.stdout.outputS, lines...)
return len(p), nil
}
w.tainted = true
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
me.stdout.outputS = append(me.stdout.outputS, lines...)
tk := me.stdout.tk
if tk == nil {
return len(p), nil
}
if tk.v == nil {
// redo this old code
v, _ := me.baseGui.View("msg")
if v != nil {
tk.v = v
}
return len(p), nil
}
tk.refreshStdout()
return len(p), nil
}
// lets the user page up and down through the stdout buffer
func (tk *guiWidget) refreshStdout() {
if len(me.stdout.outputS) < me.stdout.h+me.stdout.pager {
// log.Info(fmt.Sprintf("buffer too small=%d len(%d)", me.stdout.pager, len(me.stdout.outputS)))
var cur []string
cur = append(cur, me.stdout.outputS...)
slices.Reverse(cur)
tk.v.Clear()
fmt.Fprintln(tk.v, strings.Join(cur, "\n"))
return
}
var cur []string
// chop off the last lines in the buffer
chop := len(me.stdout.outputS) - (me.stdout.pager + me.stdout.h)
cur = append(cur, me.stdout.outputS[chop:chop+me.stdout.h]...)
slices.Reverse(cur)
tk.v.Clear()
fmt.Fprintln(tk.v, strings.Join(cur, "\n"))
}

View File

@ -1,5 +1,5 @@
// LICENSE: same as the go language itself
// Copyright 2023 WIT.COM
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// all structures and variables are local (aka lowercase)
// since the plugin should be isolated to access only
@ -10,102 +10,150 @@ package main
import (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/lib/protobuf/guipb"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
var initOnce sync.Once // run initPlugin() only once
// It's probably a terrible idea to call this 'me'
// 2025 note: doesn't seem terrible to call this 'me' anymore. notsure.
var me config
var showDebug bool = true
var showHelp bool = true
var redoWidgets bool = true
// This is the window that is currently active
// var currentWindow *tree.Node
// todo: move all this to a protobuf. then, redo all this mess.
// it got me here, but now it's time to clean it up for good
// I can't get a GO plugins that use protobuf to load yet (versioning mismatch)
type config struct {
baseGui *gocui.Gui // the main gocui handle
// rootNode *node // the base of the binary tree. it should have id == 0
treeRoot *tree.Node // the base of the binary tree. it should have id == 0
myTree *tree.TreeInfo
ctrlDown *tree.Node // shown if you click the mouse when the ctrl key is pressed
currentWindow *guiWidget // this is the current tab or window to show
logStdout *tree.Node // where to show STDOUT
startOutputW int
startOutputH int
helpLabel *gocui.View
// this is a floating widget that we show whenever the user clicks on a
// dropdown menu or combobox
// the dropdown widget to select dropdrown lists
dropdownV *guiWidget
dropdownW *guiWidget // grab the dropdown choices from this widget
// When the widget has a frame, like a button, it adds 2 lines runes on each side
// so you need 3 char spacing in each direction to not have them overlap
// the amount of padding when there is a frame
FramePadW int `default:"1" dense:"0"`
FramePadH int `default:"1" dense:"0"`
PadW int `default:"1" dense:"0"`
PadH int `default:"1" dense:"0"`
// how far down to start Window or Tab headings
WindowW int `default:"8" dense:"0"`
WindowH int `default:"-1"`
TabW int `default:"5" dense:"0"`
TabH int `default:"1" dense:"0"`
// additional amount of space to put between window & tab widgets
WindowPadW int `default:"8" dense:"0"`
TabPadW int `default:"4" dense:"0"`
// additional amount of space to indent on a group
GroupPadW int `default:"2" dense:"1"`
// additional amount of space to indent on a group
BoxPadW int `default:"2" dense:"1"`
// additional amount of space to indent on a group
GridPadW int `default:"2" dense:"1"`
// the raw beginning of each window (or tab)
RawW int `default:"1"`
RawH int `default:"5"`
// offset for the hidden widgets
FakeW int `default:"20"`
padded bool // add space between things like buttons
bookshelf bool // do you want things arranged in the box like a bookshelf or a stack?
canvas bool // if set to true, the windows are a raw canvas
menubar bool // for windows
stretchy bool // expand things like buttons to the maximum size
margin bool // add space around the frames of windows
// writeMutex protects locks the write process
writeMutex sync.Mutex
// used for listWidgets() debugging
depth int
baseGui *gocui.Gui // the main gocui handle
treeRoot *tree.Node // the base of the binary tree. it should have id == 0
myTree *tree.TreeInfo // ?
currentWindow *guiWidget // this is the current tab or window to show
ok bool // if the user doesn't hit a key or move the mouse, gocui doesn't really start
firstWindowOk bool // allows the init to wait for the first window from the application
refresh bool // redraw everything?
ctrlDown *tree.Node // shown if you click the mouse when the ctrl key is pressed
helpLabel *gocui.View // ?
showHelp bool // toggle boolean for the help menu (deprecate?)
FirstWindowW int `default:"2"` // how far over to start window #1
FirstWindowH int `default:"0"` // how far down to start window #1
FramePadW int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side
FramePadH int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side
PadW int `default:"1" dense:"0"` // pad spacing
PadH int `default:"1" dense:"0"` // pad spacing
WindowW int `default:"8" dense:"0"` // how far down to start Window or Tab headings
WindowH int `default:"-1"` // how far down to start Window or Tab headings
TabW int `default:"5" dense:"0"` // how far down to start Window or Tab headings
TabH int `default:"1" dense:"0"` // how far down to start Window or Tab headings
WindowPadW int `default:"8" dense:"0"` // additional amount of space to put between window & tab widgets
TabPadW int `default:"4" dense:"0"` // additional amount of space to put between window & tab widgets
GroupPadW int `default:"2" dense:"1"` // additional amount of space to indent on a group
BoxPadW int `default:"2" dense:"1"` // additional amount of space to indent on a box
GridPadW int `default:"2" dense:"1"` // additional amount of space to indent on a grid
RawW int `default:"1"` // the raw beginning of each window (or tab)
RawH int `default:"5"` // the raw beginning of each window (or tab)
FakeW int `default:"20"` // offset for the hidden widgets
DropdownId int `default:"-78"` // the widget id to use
padded bool // add space between things like buttons
bookshelf bool // do you want things arranged in the box like a bookshelf or a stack?
canvas bool // if set to true, the windows are a raw canvas
menubar bool // for windows
stretchy bool // expand things like buttons to the maximum size
margin bool // add space around the frames of windows
writeMutex sync.Mutex // writeMutex protects writes to *guiWidget (it's global right now maybe)
ecount int // counts how many mouse and keyboard events have occurred
supermouse bool // prints out every widget found while you move the mouse around
depth int // used for listWidgets() debugging
newWindowTrigger chan *guiWidget // work around hack to redraw windows a bit after NewWindow()
stdout stdout // information for the STDOUT window
dropdown dropdown // the dropdown menu
textbox dropdown // the textbox popup window
BG dropdown // the background widget
notify libnotify // emulates the desktop libnotify menu
allwin []*guiWidget // for tracking which window is next
dark bool // use a 'dark' color palette
mouse mouse // mouse settings
showDebug bool // todo: move this into config struct
debug bool // todo: move this into config struct
starttime time.Time // checks how long it takes on startup
winchW int // used to detect SIGWINCH
winchH int // used to detect SIGWINCH
outf *os.File // hacks for capturing stdout
}
// deprecate these
var (
initialMouseX, initialMouseY, xOffset, yOffset int
globalMouseDown, msgMouseDown, movingMsg bool
)
// stuff controlling how the mouse works
type mouse struct {
down time.Time // when the mouse was pressed down
up time.Time // when the mouse was released. used to detect click vs drag
clicktime time.Duration // how long is too long for a mouse click vs drag
mouseUp bool // is the mouse up?
double bool // user is double clicking
doubletime time.Duration // how long is too long for double click
resize bool // mouse is resizing something
downW int // where the mouse was pressed down
downH int // where the mouse was pressed down
currentDrag *guiWidget // what widget is currently being moved around
}
// settings for the stdout window
type stdout struct {
tk *guiWidget // where to show STDOUT
wId int // the widget id
w int // the width
h int // the height
outputOnTop bool // is the STDOUT window on top?
outputOffscreen bool // is the STDOUT window offscreen?
startOnscreen bool // start the output window onscreen?
disable bool // disable the stdout window. do not change os.Stdout & os.Stderr
lastW int // the last 'w' location (used to move from offscreen to onscreen)
lastH int // the last 'h' location (used to move from offscreen to onscreen)
// mouseOffsetW int // the current 'w' offset
// mouseOffsetH int // the current 'h' offset
init bool // moves the window offscreen on startup
outputS []string // the buffer of all the output
pager int // allows the user to page through the buffer
}
// settings for the dropdown window
type dropdown struct {
tk *guiWidget // where to show STDOUT
callerTK *guiWidget // which widget called the dropdown menu
items []string // what is currently in the menu
w int // the width
h int // the height
active bool // is the dropdown menu currently in use?
init bool // moves the window offscreen on startup
// Id int `default:"-78"` // the widget id to use
wId int `default:"-78"` // the widget id to use
}
// settings for the dropdown window
type internalTK struct {
once sync.Once // for init
tk *guiWidget // where to show STDOUT
callerTK *guiWidget // which widget called the dropdown menu
wId int // the widget id to use
active bool // is the internal widget currently in use?
offsetW int // width offset
offsetH int // height offset
}
// the desktop libnotify menu
type libnotify struct {
clock internalTK // widget for the clock
icon internalTK // libnotify menu icon
window internalTK // the libnotify menu
help internalTK // the help menu
}
// this is the gocui way
// corner starts at in the upper left corner
@ -118,115 +166,86 @@ func (r *rectType) Width() int {
}
func (r *rectType) Height() int {
if r.h0 == 0 && r.h1 == 0 {
// edge case. only return 0 for these
return 0
}
if r.h1 == r.h0 {
// if they are equal, it's actually height = 1
return 1
}
if r.h1-r.h0 < 1 {
// can't have negatives. something is wrong. return 1 for now
return 1
}
return r.h1 - r.h0
}
// settings that are window specific
type window struct {
windowFrame *guiWidget // this is the frame for a window widget
wasDragged bool // indicates the window was dragged. This keeps it from being rearranged
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
selectedTab *tree.Node // for a window, this is currently selected tab
active bool // means this window is the active one
order int // what level the window is on
// resize bool // only set the title once
collapsed bool // only show the window title bar
dense bool // true if the window is dense
large bool // true if the window is huge
pager int // allows the user to page through the window
}
type colorT struct {
frame gocui.Attribute
fg gocui.Attribute
bg gocui.Attribute
selFg gocui.Attribute
selBg gocui.Attribute
name string
}
type guiWidget struct {
// the gocui package variables
v *gocui.View // this is nil if the widget is not displayed
cuiName string // what gocui uses to reference the widget
WidgetType widget.WidgetType
// tw *toolkit.Widget
parent *guiWidget
children []*guiWidget
node *tree.Node
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
value string
checked bool
// the actual text to display in the console
labelN string
vals []string // dropdown menu items
active bool
enable bool
defaultColor *colorT // store the color to go back to
// hidden bool
// AtW int
// AtH int
// direction widget.Orientation
// progname string
// the logical size of the widget
// For example, 40x12 would be the center of a normal terminal
// size rectType
// the actual gocui display view of this widget
// sometimes this isn't visible like with a Box or Grid
gocuiSize rectType
startW int
startH int
isCurrent bool // is this the currently displayed Window or Tab?
isFake bool // widget types like 'box' are 'false'
// used to track the size of grids
widths map[int]int // how tall each row in the grid is
heights map[int]int // how wide each column in the grid is
tainted bool
frame bool
// for a window, this is currently selected tab
selectedTab *tree.Node
// what color to use
color *colorT
}
// from the gocui devs:
// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (w *guiWidget) Write(p []byte) (n int, err error) {
w.tainted = true
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
var tk *guiWidget
tk = me.logStdout.TK.(*guiWidget)
if tk.v == nil {
// optionally write the output to /tmp
s := fmt.Sprint(string(p))
s = strings.TrimSuffix(s, "\n")
fmt.Fprintln(outf, s)
v, _ := me.baseGui.View("msg")
if v != nil {
// fmt.Fprintln(outf, "found msg")
tk.v = v
}
} else {
// display the output in the gocui window
tk.v.Clear()
s := fmt.Sprint(string(p))
s = strings.TrimSuffix(s, "\n")
tmp := strings.Split(s, "\n")
outputS = append(outputS, tmp...)
if len(outputS) > outputH {
l := len(outputS) - outputH
outputS = outputS[l:]
}
fmt.Fprintln(tk.v, strings.Join(outputS, "\n"))
}
return len(p), nil
v *gocui.View // this is nil if the widget is not displayed
cuiName string // what gocui uses to reference the widget (usually "TK <widgetId>"
parent *guiWidget // mirrors the binary node tree
children []*guiWidget // mirrors the binary node tree
node *tree.Node // the pointer back to the tree
pb *guipb.Widget // the guipb Widget
wtype widget.WidgetType // used for Tables for now. todo: fix this correctly
windowFrame *guiWidget // this is the frame for a window widget
internal bool // indicates the widget is internal to gocui and should be treated differently
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
window window // holds information specific only to Window widgets
value string // ?
checked bool // ?
labelN string // the actual text to display in the console
vals []string // dropdown menu items
enable bool // ?
gocuiSize rectType // should mirror the real display size. todo: verify these are accurate. they are not yet
full rectType // full size of children (used by widget.Window, etc)
force rectType // force widget within these boundries (using this to debug window dragging)
startW int // ?
startH int // ?
lastW int // used during mouse dragging
lastH int // used during mouse dragging
isFake bool // widget types like 'box' are 'false'
widths map[int]int // how tall each row in the grid is
heights map[int]int // how wide each column in the grid is
tainted bool // ?
frame bool // ?
selectedTab *tree.Node // for a window, this is currently selected tab
color *colorT // what color to use
colorLast colorT // the last color the widget had
defaultColor *colorT // the default colors // TODO: make a function for this instead
}
// THIS IS GO COMPILER MAGIC
// this sets the `default` in the structs above on init()
// this is cool code. thank the GO devs for this code and Alex Flint for explaining it to me
func Set(ptr interface{}, tag string) error {
if reflect.TypeOf(ptr).Kind() != reflect.Ptr {
log.Log(ERROR, "Set() Not a pointer", ptr, "with tag =", tag)
@ -246,12 +265,11 @@ func Set(ptr interface{}, tag string) error {
}
func setField(field reflect.Value, defaultVal string, name string) error {
if !field.CanSet() {
// log("setField() Can't set value", field, defaultVal)
return fmt.Errorf("Can't set value\n")
} else {
log.Log(NOW, "setField() Can set value", name, defaultVal)
// log.Log(NOW, "setField() Can set value", name, defaultVal)
}
switch field.Kind() {

106
table.go Normal file
View File

@ -0,0 +1,106 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"slices"
"go.wit.com/lib/protobuf/guipb"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
/*
func initGridPB(pb *guipb.Widget) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.pb = pb
w.parent = me.treeRoot.TK.(*guiWidget)
w.wtype = widget.Window
w.cuiName = fmt.Sprintf("%d %s", pb.Id, "TK")
w.labelN = pb.Name
return w
}
*/
func initGridPB(pb *guipb.Widget) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.pb = pb
w.wtype = widget.Grid
w.cuiName = fmt.Sprintf("%d %s", pb.Id, "TK")
w.labelN = pb.Name
return w
}
func showTable(t *guipb.Table) {
log.Info("gocui: should show table here")
if t == nil {
return
}
log.Info("gocui: table.Title", t.Title)
// log.Info("gocui: need to add window here id =", t.Window.Id, t.Window.Name)
if t.Grid == nil {
log.Info("gocui: missing grid widget. tree plugin error")
return
}
root := me.treeRoot.TK.(*guiWidget)
parent := root.findWidgetById(int(t.Parent.Id))
if parent == nil {
log.Info("gocui: show table error. parent.Id not found", t.Parent.Id)
return
}
log.Info("gocui: need to add grid here id =", t.Grid.Id)
grid := initGridPB(t.Grid)
grid.parent = parent
}
func enableWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Enable()
}
func disableWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Disable()
}
func showWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Show()
}
func hideWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
if n.WidgetType == widget.Window {
tk.windowFrame.Hide()
tk.hideWidgets()
}
tk.Hide()
tk.deleteWidget()
}
func (tk *guiWidget) deleteWidget() {
log.Log(INFO, "gocui deleteWidget() looking for child to delete:", tk.cuiName)
p := tk.parent
for i, child := range p.children {
if tk == child {
log.Log(INFO, "deleteWidget() found parent with child to delete:", i, child.cuiName, child.WidgetId())
p.children = slices.Delete(p.children, i, i+1)
}
}
tk.deleteTree()
}
func (tk *guiWidget) deleteTree() {
for _, child := range tk.children {
child.deleteTree()
}
tk.Hide()
}

135
textbox.go Normal file
View File

@ -0,0 +1,135 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// simulates a dropdown menu in gocui
import (
"strings"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
)
func (tk *guiWidget) forceSizes(r *rectType) {
tk.gocuiSize.w0 = r.w0
tk.gocuiSize.w1 = r.w1
tk.gocuiSize.h0 = r.h0
tk.gocuiSize.h1 = r.h1
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
tk.force.w0 = r.w0
tk.force.w1 = r.w1
tk.force.h0 = r.h0
tk.force.h1 = r.h1
}
func initTextbox() {
if me.textbox.tk == nil {
// should only happen once
me.textbox.tk = makeNewFlagWidget(me.textbox.wId)
// me.textbox.tk.dumpWidget("init() textbox")
}
}
func (callertk *guiWidget) prepTextbox() {
initTextbox()
if me.textbox.tk == nil {
log.Log(GOCUI, "prepTextbox() Is Broken")
return
}
r := new(rectType)
// startW, startH := tk.Position()
r.w0 = callertk.gocuiSize.w0 + 4
r.h0 = callertk.gocuiSize.h0 + 3
r.w1 = r.w0 + 24
r.h1 = r.h0 + 2
me.textbox.tk.forceSizes(r)
// me.textbox.tk.dumpWidget("after sizes")
me.textbox.callerTK = callertk
// showTextbox(callertk.String())
}
func showTextbox(callers string) {
// tk := me.textbox.tk
// me.textbox.tk.dumpWidget("after sizes")
// me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this
if me.textbox.tk.v == nil {
log.Info("wtf went wrong")
return
}
me.textbox.tk.setColorModal()
me.textbox.tk.v.Clear()
cur := strings.TrimSpace(callers)
// log.Info("setting textbox string to:", cur)
me.textbox.tk.v.WriteString(cur)
me.textbox.tk.v.Editable = true
me.textbox.tk.v.Wrap = true
r := me.textbox.tk.gocuiSize
me.baseGui.SetView(me.textbox.tk.cuiName, r.w0, r.h0, r.w1, r.h1, 0)
me.baseGui.SetCurrentView(me.textbox.tk.v.Name())
// bind the enter key to a function so we can close the textbox
me.baseGui.SetKeybinding(me.textbox.tk.v.Name(), gocui.KeyEnter, gocui.ModNone, theCloseTheTextbox)
me.textbox.active = true
// me.textbox.dumpWidget("showTextbox()")
}
func theCloseTheTextbox(g *gocui.Gui, v *gocui.View) error {
textboxClosed()
return nil
}
// updates the text and sends an event back to the application
func textboxClosed() {
// get the text the user entered
var newtext string
if me.textbox.tk.v == nil {
newtext = ""
} else {
newtext = me.textbox.tk.v.ViewBuffer()
}
newtext = strings.TrimSpace(newtext)
me.textbox.active = false
me.textbox.tk.Hide()
// log.Info("textbox closed with text:", newtext, me.textbox.callerTK.cuiName)
if me.notify.clock.tk.v != nil {
me.baseGui.SetCurrentView("help")
} else {
me.baseGui.SetCurrentView("msg")
}
// change the text of the caller widget
me.textbox.callerTK.SetText(newtext)
me.textbox.callerTK.node.SetCurrentS(newtext)
// send an event from the plugin with the new string
me.myTree.SendUserEvent(me.textbox.callerTK.node)
win := me.textbox.callerTK.findParentWindow()
if win != nil {
// win.dumpWidget("redraw this!!!")
tk := me.textbox.callerTK
// me.textbox.callerTK.dumpWidget("resize this!!!")
me.textbox.callerTK.Size()
me.textbox.callerTK.placeWidgets(tk.gocuiSize.w0-4, tk.gocuiSize.h0-4)
// tk.dumpWidget("resize:" + tk.String())
win.makeWindowActive()
}
}

24
tree.go
View File

@ -1,24 +0,0 @@
package main
/*
This is reference code for toolkit developers
*/
import (
"go.wit.com/widget"
)
// Other goroutines must use this to access the GUI
//
// You can not acess / process the GUI thread directly from
// other goroutines. This is due to the nature of how
// Linux, MacOS and Windows work (they all work differently. suprise. surprise.)
//
// this sets the channel to send user events back from the plugin
func Callback(guiCallback chan widget.Action) {
me.myTree.Callback(guiCallback)
}
func PluginChannel() chan widget.Action {
return me.myTree.PluginChannel()
}

117
treeAdd.go Normal file
View File

@ -0,0 +1,117 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
var fakeStartWidth int = me.FakeW
var fakeStartHeight int = me.TabH + me.FramePadH
// setup fake labels for non-visible things off screen
func setFake(n *tree.Node) {
var w *guiWidget
w = n.TK.(*guiWidget)
w.isFake = true
w.gocuiSetWH(fakeStartWidth, fakeStartHeight)
fakeStartHeight += w.gocuiSize.Height()
// TODO: use the actual max hight of the terminal window
if fakeStartHeight > 24 {
fakeStartHeight = me.TabH
fakeStartWidth += me.FakeW
}
}
// mostly just sets the colors of things
func addWidget(n *tree.Node) {
if !me.ok {
log.Log(INFO, "addWidget() START NOT OKAY")
log.Log(INFO, "addWidget() START NOT OKAY")
log.Log(INFO, "addWidget() START NOT OKAY")
waitOK()
}
tk := n.TK.(*guiWidget)
log.Log(INFO, "setStartWH() w.id =", n.WidgetId, "n.name", n.String())
switch n.WidgetType {
case widget.Root:
log.Log(INFO, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.String())
// tk.color = &colorRoot
setFake(n)
return
case widget.Flag:
// tk.color = &colorFlag
setFake(n)
return
case widget.Window:
tk.frame = false
tk.labelN = tk.GetText() + " X"
// tk.setColor(&colorWindow)
me.newWindowTrigger <- tk
redoWindows(0, 0)
return
case widget.Stdout:
tk.labelN = "moreSTDOUT"
n.State.ProgName = "moreSTDOUT"
n.State.Label = "moreSTDOUT"
tk.isFake = true
return
case widget.Tab:
// tk.color = &colorTab
return
case widget.Button:
tk.setColorButton()
if tk.IsEnabled() {
} else {
tk.setColorDisable()
}
return
case widget.Checkbox:
tk.setColorInput()
tk.labelN = "X " + n.State.Label
return
case widget.Dropdown:
tk.setColorInput()
return
case widget.Textbox:
n.State.Label = ""
tk.labelN = " "
// tk.color = &colorDropdown
tk.setColorInput()
return
case widget.Combobox:
// tk.color = &colorCombobox
tk.setColorInput()
return
case widget.Box:
// tk.color = &colorBox
tk.isFake = true
setFake(n)
return
case widget.Grid:
// tk.color = &colorGrid
tk.isFake = true
setFake(n)
return
case widget.Group:
tk.setColorLabel()
tk.frame = false
return
case widget.Label:
tk.setColorLabel()
tk.frame = false
return
default:
/*
if n.IsCurrent() {
n.updateCurrent()
}
*/
}
tk.dumpWidget("addWidget()unknown")
}

271
treeDraw.go Normal file
View File

@ -0,0 +1,271 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"errors"
"fmt"
"strconv"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
// don't draw widgets that are too far down the window
func (tk *guiWidget) doNotDraw() bool {
var check bool
switch tk.WidgetType() {
case widget.Button:
check = true
case widget.Label:
check = true
default:
}
if !check {
return false
}
win := tk.findParentWindow()
if win == nil {
// don't draw anything if you can't find the parent window
return true
}
h := tk.gocuiSize.h0 - win.gocuiSize.h0
if h > 20 {
return true
}
return false
}
// page widgets in the window
func (tk *guiWidget) pageWidget() *rectType {
r := new(rectType)
var check bool
switch tk.WidgetType() {
case widget.Button:
check = true
case widget.Label:
check = true
default:
}
if !check {
return nil
}
win := tk.findParentWindow()
if win == nil {
// don't draw anything if you can't find the parent window
return nil
}
r.w0 = tk.gocuiSize.w0
r.h0 = tk.gocuiSize.h0
r.w1 = tk.gocuiSize.w1
r.h1 = tk.gocuiSize.h1
// r.h0 = tk.gocuiSize.h0 - win.gocuiSize.h0
if r.h0 > 20 {
return r
}
return r
}
// display's the text of the widget in gocui
// deletes the old view if it exists and recreates it
func (tk *guiWidget) drawView() {
var err error
log.Log(INFO, "drawView() START", tk.WidgetType(), tk.String())
if me.baseGui == nil {
log.Log(ERROR, "drawView() ERROR: me.baseGui == nil", tk)
return
}
if tk.cuiName == "" {
log.Log(ERROR, "drawView() tk.cuiName was not set for widget", tk)
tk.cuiName = strconv.Itoa(tk.WidgetId()) + " TK"
}
log.Log(INFO, "drawView() labelN =", tk.labelN)
// this deletes the button from gocui
me.baseGui.DeleteView(tk.cuiName)
tk.v = nil
a := tk.gocuiSize.w0
b := tk.gocuiSize.h0
c := tk.gocuiSize.w1
d := tk.gocuiSize.h1
/*
// testing code for paging large windows
if tk.doNotDraw() {
return
}
if tk.window.pager != 0 {
if r := tk.pageWidget(); r == nil {
// if nil, draw whatever it is anyway
} else {
if r.Width() == 0 && r.Height() == 0 {
// don't draw empty stuff
return
}
a = r.w0
b = r.h0
c = r.w1
d = r.h1
}
}
if tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Flag {
if tk.window.pager != 0 {
if tk.gocuiSize.Height() > 40 {
tk.window.large = true
tk.gocuiSize.h1 = tk.gocuiSize.h0 + 40
d = tk.gocuiSize.h1
}
}
}
*/
// this is all terrible. This sets the title. kinda
if tk.WidgetType() == widget.Window {
tk.textResize()
tk.full.w0 = tk.force.w0
tk.full.h0 = tk.force.h0
// for windows, make it the full size
a = tk.full.w0
b = tk.full.h0
c = tk.full.w0 + tk.gocuiSize.Width()
d = tk.full.h0 + tk.gocuiSize.Height()
} else {
if tk.internal {
// do nothing
} else {
tk.textResize() // resize gocui frame to the widget text
}
a = tk.gocuiSize.w0
b = tk.gocuiSize.h0
c = tk.gocuiSize.w1
d = tk.gocuiSize.h1
}
tk.v, err = me.baseGui.SetView(tk.cuiName, a, b, c, d, 0)
if err == nil {
tk.dumpWidget("drawView() err")
log.Log(ERROR, "drawView() internal plugin error err = nil")
return
}
if !errors.Is(err, gocui.ErrUnknownView) {
tk.dumpWidget("drawView() err")
log.Log(ERROR, "drawView() internal plugin error error.IS()", err)
return
}
if tk.v == nil {
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
return
}
// this actually sends the text to display to gocui
tk.v.Wrap = true
tk.v.Frame = tk.frame
tk.v.Clear()
fmt.Fprint(tk.v, tk.labelN)
// tmp hack to disable buttons on window open
if tk.WidgetType() == widget.Button {
if tk.IsEnabled() {
} else {
tk.setColorDisable()
}
}
switch tk.WidgetType() {
case widget.Button:
if tk.IsEnabled() {
if tk.isWindowDense() && tk.isInGrid() {
tk.setColorButtonDense()
} else {
tk.setColorButton()
}
} else {
tk.setColorDisable()
}
default:
}
if tk.v == nil {
log.Info("MUTEX FAIL 2. tk.v was deleted somehow tk.v == nil here in drawView()")
log.Info("MUTEX FAIL 2. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL 2. tk.v == nil here in drawView()")
return
}
// if you don't do this here, it will be black & white only
if tk.color != nil {
tk.v.FrameColor = tk.color.frame
tk.v.FgColor = tk.color.fg
tk.v.BgColor = tk.color.bg
tk.v.SelFgColor = tk.color.selFg
tk.v.SelBgColor = tk.color.selBg
}
log.Log(INFO, "drawView() END")
}
func (w *guiWidget) DrawAt(offsetW, offsetH int) {
// w.setColor(&colorActiveW)
w.placeWidgets(offsetW, offsetH) // compute the sizes & places for each widget
// w.dumpWidget(fmt.Sprintf("DrawAt(%d,%d)", offsetW, offsetH))
}
func (w *guiWidget) simpleDrawAt(offsetW, offsetH int) {
// w.setColor(&colorActiveW)
w.dumpWidget("simpleDrawAt()")
}
// display the widgets in the binary tree
func (w *guiWidget) drawTree(draw bool) {
if w == nil {
return
}
w.dumpWidget("in drawTree()")
if draw {
// w.textResize()
w.Show()
} else {
w.Hide()
}
for _, child := range w.children {
child.drawTree(draw)
}
}
func (w *guiWidget) Show() {
if w.isFake {
// don't display fake widgets
return
}
if w.Hidden() {
// recursively checks if the parent is hidden
// never show hidden widgets
return
}
if me.debug {
w.dumpWidget("drawView()")
}
w.drawView()
}

90
treeInit.go Normal file
View File

@ -0,0 +1,90 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
/*
DO NOT EDIT THIS FILE
this file is the same for every GUI toolkit plugin
when you are making a new GUI toolkit plugin for
a specific toolkit, you just need to define these
functions.
for example, in the "gocui" toolkit, the functions
below are what triggers the "gocui" GO package
to draw labels, buttons, windows, etc
If you are starting out trying to make a new GUI toolkit,
all you have to do is copy this file over. Then
work on making these functions. addWidget(), setText(), etc.
That's it!
*/
package main
/*
This is reference code for toolkit developers
This is how information is passed in GO back to the application
via the GO 'plugin' concept
TODO: switch this to protocol buffers
*/
import (
"time"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
// Other goroutines must use this to access the GUI
//
// You can not acess / process the GUI thread directly from
// other goroutines. This is due to the nature of how
// Linux, MacOS and Windows work (they all work differently. suprise. surprise.)
//
// this sets the channel to send user events back from the plugin
func Callback(guiCallback chan widget.Action) {
me.myTree.Callback(guiCallback)
}
func PluginChannel() chan widget.Action {
initOnce.Do(initPlugin)
for {
if me.myTree != nil {
break
}
log.Info("me.myTree == nil")
time.Sleep(300 * time.Millisecond)
}
return me.myTree.PluginChannel()
}
func FrozenChannel() chan widget.Action {
return me.myTree.FrozenChannel()
}
func initTree() *tree.TreeInfo {
t := tree.New()
t.PluginName = PLUGIN
t.Add = newAdd
t.SetTitle = setTitle
t.SetLabel = setLabel
t.SetText = setText
t.AddText = addText
t.Enable = enableWidget
t.Disable = disableWidget
t.Show = showWidget
t.Hide = hideWidget
t.SetChecked = setChecked
t.ToolkitInit = toolkitInit
t.ToolkitClose = toolkitClose
t.ShowTable = showTable
return t
}

126
treeWidget.go Normal file
View File

@ -0,0 +1,126 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"strconv"
"strings"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func initWidget(n *tree.Node) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.node = n
w.cuiName = strconv.Itoa(w.WidgetId()) + " TK"
// w.WidgetType() = n.WidgetType
w.labelN = n.State.Label
if w.labelN == "" {
// remove this debugging hack once things are stable and fixed
w.labelN = n.GetProgName()
}
w.frame = true
w.enable = n.State.Enable
if n.WidgetType == widget.Root {
log.Log(INFO, "setupWidget() FOUND ROOT w.id =", n.WidgetId)
}
if n.WidgetType == widget.Grid {
w.widths = make(map[int]int) // how tall each row in the grid is
w.heights = make(map[int]int) // how wide each column in the grid is
}
p := n.Parent
if p == nil {
log.Log(ERROR, "parent == nil", w.String(), n.WidgetId, w.WidgetType())
return w
}
if p.TK == nil {
if n.WidgetId == 0 {
// this is a normal init condition
} else {
log.Log(ERROR, "parent.TK == nil", w.String(), n.WidgetId, w.WidgetType())
}
return w
}
// set the parent and append to parent children
var ptk *guiWidget
ptk = p.TK.(*guiWidget)
w.parent = ptk
ptk.children = append(ptk.children, w)
return w
}
func setupCtrlDownWidget() {
a := new(widget.Action)
a.ProgName = "ctrlDown"
a.WidgetType = widget.Dialog
a.WidgetId = -1
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.ctrlDown = n
}
func (w *guiWidget) deleteView() {
// make sure the view isn't really there
// log.Log(GOCUI, "deleteView()", w.cuiName, w.WidgetType(), w.WidgetId())
me.baseGui.DeleteView(w.cuiName)
w.v = nil
}
func (tk *guiWidget) String() string {
// deprecate this?
curval := strings.TrimSpace(tk.labelN)
if curval != "" {
return curval
}
curval = strings.TrimSpace(tk.GetLabel())
if curval != "" {
return curval
}
curval = tk.GetText()
if curval != "" {
return curval
}
curval = tk.node.String()
if curval != "" {
return curval
}
curval = strings.TrimSpace(tk.node.ProgName())
if curval != "" {
return curval
}
return ""
}
func (tk *guiWidget) Visible() bool {
if tk == nil {
return false
}
if tk.v == nil {
return false
}
tk.v.Visible = true
return true
}
func (tk *guiWidget) Hide() {
tk.deleteView()
}
func (tk *guiWidget) SetVisible(b bool) {
if b {
tk.Show()
} else {
tk.Hide()
}
}

68
view.go
View File

@ -1,54 +1,56 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"bufio"
"strings"
"go.wit.com/log"
"go.wit.com/widget"
)
func splitLines(s string) []string {
var lines []string
sc := bufio.NewScanner(strings.NewReader(s))
for sc.Scan() {
lines = append(lines, sc.Text())
// expands the gocuiSize rectangle to fit
// all the text in tk.labelN
func (tk *guiWidget) textResize() {
var w, h int = 0, 0
for _, s := range strings.Split(tk.labelN, "\n") {
s = strings.TrimSpace(s)
// log.Log(INFO, "textResize() len =", len(s), i, s)
if w < len(s) {
w = len(s)
}
h += 1
}
return lines
// todo: fix all this old code
if tk.WidgetType() == widget.Textbox {
if w < 5 {
w = 5
}
}
// this is old code. now move this somewhere smarter
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW // TODO: move this FramePadW out of here
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH // TODO: fix this size computation
}
func (w *guiWidget) textResize() bool {
// w := n.tk
var width, height int = 0, 0
var changed bool = false
for i, s := range splitLines(w.labelN) {
log.Log(INFO, "textResize() len =", len(s), i, s)
if width < len(s) {
width = len(s)
}
height += 1
// deletes every view
func (w *guiWidget) hideWindow() {
if w == nil {
return
}
if w.gocuiSize.w1 != w.gocuiSize.w0+width+me.FramePadW {
w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW
changed = true
w.Hide()
for _, child := range w.children {
child.hideWindow()
}
if w.gocuiSize.h1 != w.gocuiSize.h0+height+me.FramePadH {
w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH
changed = true
}
if changed {
// w.showWidgetPlacement("textResize() changed")
}
return changed
}
func (w *guiWidget) hideWidgets() {
if w == nil {
return
}
w.isCurrent = false
switch w.node.WidgetType {
switch w.WidgetType() {
case widget.Root:
case widget.Flag:
case widget.Window:
@ -88,7 +90,7 @@ func (w *guiWidget) hideFake() {
func (w *guiWidget) showFake() {
if w.isFake {
w.drawView()
w.showWidgetPlacement("showFake:")
w.dumpWidget("in showFake()")
}
for _, child := range w.children {
child.showFake()

198
widget.go
View File

@ -1,198 +0,0 @@
package main
import (
"strconv"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func initWidget(n *tree.Node) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.node = n
w.cuiName = strconv.Itoa(w.node.WidgetId) + " TK"
w.WidgetType = n.WidgetType
w.labelN = n.State.Label
if w.labelN == "" {
// remove this debugging hack once things are stable and fixed
w.labelN = n.GetProgName()
}
w.frame = true
w.enable = n.State.Enable
if n.WidgetType == widget.Root {
log.Log(INFO, "setupWidget() FOUND ROOT w.id =", n.WidgetId)
}
if n.WidgetType == widget.Grid {
w.widths = make(map[int]int) // how tall each row in the grid is
w.heights = make(map[int]int) // how wide each column in the grid is
}
p := n.Parent
if p == nil {
log.Log(ERROR, "parent == nil", w.String(), n.WidgetId, w.WidgetType)
return w
}
if p.TK == nil {
log.Log(ERROR, "parent.TK == nil", w.String(), n.WidgetId, w.WidgetType)
return w
}
// set the parent and append to parent children
var ptk *guiWidget
ptk = p.TK.(*guiWidget)
w.parent = ptk
ptk.children = append(ptk.children, w)
return w
}
func setupCtrlDownWidget() {
a := new(widget.Action)
a.ProgName = "ctrlDown"
a.WidgetType = widget.Dialog
a.WidgetId = -1
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.ctrlDown = n
}
func (w *guiWidget) deleteView() {
// make sure the view isn't really there
log.Log(GOCUI, "deleteView()", w.cuiName, w.WidgetType, w.node.WidgetId)
me.baseGui.DeleteView(w.cuiName)
w.v = nil
}
func (w *guiWidget) IsCurrent() bool {
if w.node.WidgetType == widget.Tab {
return w.isCurrent
}
if w.node.WidgetType == widget.Window {
log.Log(GOCUI, "IsCurrent() found current window", w.cuiName, w.String())
log.Log(GOCUI, "IsCurrent() window w.isCurrent =", w.isCurrent)
return w.isCurrent
}
if w.node.WidgetType == widget.Root {
return false
}
return w.parent.IsCurrent()
}
func (tk *guiWidget) String() string {
return tk.node.String()
}
func (tk *guiWidget) Visible() bool {
if tk == nil {
return false
}
if tk.v == nil {
return false
}
tk.v.Visible = true
return true
}
func (w *guiWidget) Show() {
// always should the dropdown widget
if w == me.dropdownV {
me.dropdownV.drawView()
return
}
// don't display fake widgets
if w.isFake {
return
}
// if this isn't in the binary tree
// it's some internal widget so always display those
if w.node == nil {
w.drawView()
return
}
// always show window titles
if w.node.WidgetType == widget.Window {
w.drawView()
return
}
if w.node.WidgetType == widget.Dropdown {
log.Log(NOW, "Show() dropdown", w.cuiName, w.String())
log.Log(NOW, "Show() dropdown n.Strings() =", w.node.Strings())
}
if w.node.WidgetType == widget.Combobox {
log.Log(NOW, "Show() dropdown", w.cuiName, w.String())
log.Log(NOW, "Show() dropdown n.Strings() =", w.node.Strings())
}
// if the widget is not in the current displayed windo
// then ignore it
log.Log(GOCUI, "Show() widget =", w.cuiName, w.String())
log.Log(GOCUI, "Show() w.IsCurrent() returned", w.IsCurrent())
if !w.IsCurrent() {
log.Log(GOCUI, "Show() NOT drawing", w.cuiName, w.String())
return
}
log.Log(GOCUI, "Show() drawing", w.cuiName, w.String())
// finally, check if the widget State is hidden or not
if w.node.Hidden() {
// don't display hidden widgets
return
}
// okay, if you made it this far, then display the widget
w.drawView()
}
func (tk *guiWidget) Hide() {
tk.deleteView()
}
func (tk *guiWidget) SetVisible(b bool) {
if b {
tk.Show()
} else {
tk.Hide()
}
}
func (tk *guiWidget) findWidgetByName(name string) *guiWidget {
if tk.cuiName == name {
return tk
}
for _, child := range tk.children {
found := child.findWidgetByName(name)
if found != nil {
return found
}
}
return nil
}
func (tk *guiWidget) findWidgetByView(v *gocui.View) *guiWidget {
if tk.v == v {
return tk
}
if tk.cuiName == v.Name() {
log.Log(NOW, "findWidget() error. names are mismatched or out of sync", tk.cuiName)
log.Log(NOW, "findWidget() or maybe the view has been deleted")
// return tk
}
for _, child := range tk.children {
found := child.findWidgetByView(v)
if found != nil {
return found
}
}
return nil
}

270
window.go
View File

@ -1,28 +1,264 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"strings"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func (tk *guiWidget) setTitle(s string) {
if tk.WidgetType() != widget.Window {
return
}
if tk.v == nil {
return
}
tk.setColorWindowTitleActive()
rect := tk.gocuiSize
rect.w1 = rect.w0 + tk.full.Width() + 1
// rect.h1 = rect.h0 + 1
me.baseGui.SetView(tk.v.Name(), rect.w0-1, rect.h0, rect.w1+1, rect.h1, 0)
tk.v.Clear()
f := " %-" + fmt.Sprintf("%d", tk.full.Width()-3) + "s %s"
tmp := tk.GetLabel()
labelN := fmt.Sprintf(f, tmp, "X")
tk.v.WriteString(labelN)
}
func (tk *guiWidget) redrawWindow(w int, h int) {
if tk.WidgetType() != widget.Window {
return
}
// tk.dumpWidget(fmt.Sprintf("redrawWindow(%d,%d)", w, h))
if tk.full.Height() > 40 {
tk.window.dense = true
}
// pin the window to (w,h)
tk.gocuiSize.w0 = w
tk.gocuiSize.h0 = h
tk.gocuiSize.w1 = w + len(tk.GetLabel())
tk.labelN = tk.GetLabel() // could set XX here also but don't have final size of window yet
tk.force.w0 = w
tk.force.w1 = w
tk.force.h0 = h
tk.force.h1 = h
tk.setFullSize() // might make the green box the right size
tk.frame = false
tk.hasTabs = false
tk.DrawAt(w, h)
// tk.setColor(&colorActiveW) // sets the window to Green BG
tk.setColorWindowTitleActive()
if tk.window.collapsed {
// don't show anything but the title bar
tk.hideWindow()
return
}
tk.placeWidgets(w, h) // compute the sizes & places for each widget
// this is a test. this should not be needed
tk.full.w0 = tk.force.w0
tk.full.h0 = tk.force.h0
tk.setFullSize()
tk.Show()
if tk.v == nil {
log.Info("redrawWindow on tk.v == nil")
standardExit()
}
tk.v.Clear()
fmt.Fprint(tk.v, "ZZZ"+tk.GetText())
tk.showWidgets()
if tk.windowFrame == nil {
tk.addWindowFrameTK(0 - tk.WidgetId())
tk.windowFrame.makeTK([]string{""})
}
// this seems to correctly create the window frame
r := tk.getFullSize()
tk.windowFrame.gocuiSize.w0 = tk.force.w0
tk.windowFrame.gocuiSize.w1 = r.w1 + 1
tk.windowFrame.gocuiSize.h0 = tk.force.h0 + 2
tk.windowFrame.gocuiSize.h1 = r.h1 + 1
tk.windowFrame.full.w0 = tk.force.w0
tk.windowFrame.full.w1 = r.w1 + 1
tk.windowFrame.full.h0 = tk.force.h0 + 2
tk.windowFrame.full.h1 = r.h1 + 1
tk.windowFrame.setColorWindowFrame()
tk.windowFrame.Hide()
tk.windowFrame.Show()
// set the window frame below the window widget, but this resizes the window widget it seems
me.baseGui.SetViewBeneath(tk.windowFrame.cuiName, tk.cuiName, 1)
// so now we have to resize the window frame, but this moves it to the top?
me.baseGui.SetView(tk.windowFrame.cuiName, tk.windowFrame.full.w0, tk.windowFrame.full.h0, tk.windowFrame.full.w1, tk.windowFrame.full.h1, 0)
// so we have to redraw the widgets in the window anyway and then they will appear above he frame
tk.hideWidgets()
tk.showWidgets()
// draw the window title
tk.setTitle(tk.GetLabel())
}
// re-draws the buttons for each of the windows
func (w *guiWidget) redoWindows(nextW int, nextH int) {
var startW int = nextW
var startH int = nextH
for _, child := range w.children {
if child.node.WidgetType != widget.Window {
continue
func redoWindows(nextW int, nextH int) {
for _, tk := range findWindows() {
// tk.dumpWidget(fmt.Sprintf("redoWindowsS (%d,%d)", nextW, nextH))
if tk.window.wasDragged {
// don't move windows around the user has dragged to a certain location
tk.makeWindowActive()
} else {
w, _ := me.baseGui.Size()
if nextW > w-20 {
nextW = 0
nextH += 4
}
// probably a new window?
tk.redrawWindow(nextW, nextH)
}
// tk.dumpWidget(fmt.Sprintf("redoWindowsE (%d,%d)", nextW, nextH))
child.frame = false
child.hasTabs = false
child.gocuiSetWH(nextW, nextH)
child.Hide()
child.drawView()
sizeW := child.gocuiSize.Width()
nextW += sizeW + 4
child.redoWindows(startW+3, startH+2)
// increment the width for the next window
nextW += tk.gocuiSize.Width() + 10
// nextH += 10
}
}
func (tk *guiWidget) addWindowFrameTK(wId int) {
n := tk.addWindowFrame(wId)
tk.windowFrame = n.TK.(*guiWidget)
}
func (win *guiWidget) addWindowFrame(wId int) *tree.Node {
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.labelN = "windowFrame text"
tk.internal = true
tk.node = n
if tk.node.Parent == nil {
tk.node.Parent = me.treeRoot
}
// set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId)
// tk.color = &colorGroup
// add this new widget on the binary tree
tk.parent = win
if tk.parent == nil {
panic("addDropdown() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return n
}
func (tk *guiWidget) isWindowActive() bool {
if !(tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Stdout) {
// only allow Window or the Stdout widgets to be made active
return false
}
return tk.window.active
}
// always redraws at the corner of the gocuiSize box
func (tk *guiWidget) makeWindowActive() {
if !(tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Stdout) {
// only allow Window or the Stdout widgets to be made active
return
}
if tk.WidgetType() == widget.Stdout {
me.stdout.outputOnTop = true
} else {
// me.stdout.outputOnTop = false // ?
}
// disable and increment all the windows
for _, tk := range me.allwin {
tk.window.order += 1
tk.window.active = false
// tk.setColor(&colorWindow) // color for inactive windows
tk.setColorWindowTitle()
}
// set this window as the active one
tk.window.active = true
tk.window.order = 0
tk.redrawWindow(tk.gocuiSize.w0, tk.gocuiSize.h0)
setThingsOnTop() // sets help, Stdout, etc on the top after windows have been redrawn
/*
// print out the window list
for _, tk := range me.allwin {
log.Info("makeWindowActive() Window", tk.labelN, tk.window.active, tk.window.order)
}
*/
}
func (tk *guiWidget) makeTK(ddItems []string) {
items := strings.Join(ddItems, "\n")
tk.labelN = items
tk.SetText(items)
tk.gocuiSize.w0 = 100
tk.gocuiSize.w1 = 120
tk.gocuiSize.h0 = 15
tk.gocuiSize.h1 = 18
/*
var err error
tk.v, err = me.baseGui.SetView(tk.cuiName,
tk.gocuiSize.w0,
tk.gocuiSize.h0,
tk.gocuiSize.w1,
tk.gocuiSize.h1, 0)
if err != nil {
log.Info("makeTK() err", err)
return
}
if tk.v == nil {
return
}
tk.v.Wrap = true
tk.v.Frame = true
tk.v.Clear()
fmt.Fprint(tk.v, items)
*/
tk.Show()
}
func (win *guiWidget) checkWindowClose(w int, h int) bool {
s := fmt.Sprintf("mouse(%d,%d) ", w, h)
offW := win.full.w1 - w
offH := h - win.full.h0
s += fmt.Sprintf("offset(%d,%d)", offW, offH)
if (offW < 2) && (offH < 2) {
log.Info("attempting close on ", s, win.cuiName)
me.myTree.SendWindowCloseEvent(win.node)
// store the stdout corner for computing the drag size
return true
}
// log.Info("not attempting close on ", s, win.cuiName)
return false
}