-
Notifications
You must be signed in to change notification settings - Fork 2
/
sample_tui.py
775 lines (655 loc) · 31 KB
/
sample_tui.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
# =================================================================================
# This is an example TUI app that uses ALL of the extended widgets.
# =================================================================================
from __future__ import annotations
import os
import sys
import rich.repr
import io
import math
from contextlib import redirect_stdout
import rich
from rich import box
from rich import print
from rich.console import RenderableType
from rich.console import Group
from rich.screen import Screen
from rich.control import Control
from rich.style import Style
from rich import inspect
from rich import get_console
from rich.protocol import is_renderable
from rich.syntax import Syntax
from rich.traceback import Traceback
from rich.text import Text
from rich.panel import Panel
from rich.pretty import Pretty
from rich.align import Align
from rich.table import Table
from rich.columns import Columns
from rich.box import DOUBLE
from textual.app import App
from textual.widgets import Header, Footer, FileClick, ScrollView, DirectoryTree, Static, Placeholder
from textual.views import GridView, WindowView
from textual.layouts.grid import GridLayout
from textual._timer import Timer
from textual.widgets import Button, ButtonPressed
from textual.widget import Reactive, Widget
from textual_inputs import TextInput
# Import our extended widgets
from tui import Checkbutton, Radiobutton, RadioGroup, Droplist, ListCollapse, CliInput, HoverButton, Label
from tui import RowHeightUpdate, Tabs, Dynamic, DynamicTable, TuiPlot, PlotAxis, Erase
from tui.widgets.fixed_title_textinput import FixedTitleTextInput
from tui.views.control_panel_view import ControlPanelView
from tui.screen_driver import ScreenDriver
from tui.widgets.dynamic import RenderableUpdate
# ===================================================
# A class to render the "Graphics" tab images
# ===================================================
class PBMStereo(TuiPlot):
def __init__(
self,
style: StyleType = None,
name: str | None = None,
width: int | None = None,
height: int | None = None,
):
""" Initialize a Graphics graph with image rending test routines """
super().__init__(style, name, width, height, border=False)
self.absolute_coords = True
width = 50
self.cube_width = width
height = 44
self.img = 0
self.xcoords_left = [
int(-0.2*width),
0,
int(width/2 -0.15*width),
int(width/2 -0.05*width),
int(width-0.2*width),
width
]
self.xcoords_right = [
int(-0.3*width),
0,
int(width/2 -0.25*width),
int(width/2 +0.05*width),
int(width-0.3*width),
width
]
self.ycoords = [
0,
int(0.2*height),
int(height/2-0.07*height),
int(height/2),
int(height/2+0.15*height),
int(height/2+0.17*height),
height,
int(height+0.2*height)
]
async def next(self):
self.img += 1
if self.img > 2:
self.img = 0
def draw_cube_shape(self, x, y, xcoords, ycoords):
self.push_line_color(Style(color="white", bold=True))
# Draw back face first
self.draw_line(x + xcoords[1], y+ycoords[0], x+xcoords[5],y+ycoords[0])
self.draw_line(x + xcoords[5], y+ycoords[0], x+xcoords[5],y+ycoords[6])
self.draw_line(x + xcoords[5], y+ycoords[6], x+xcoords[1],y+ycoords[6])
self.draw_line(x + xcoords[1], y+ycoords[6], x+xcoords[1],y+ycoords[0])
# Draw leftmost triangle shape
self.draw_line(x + xcoords[1], y+ycoords[0], x+xcoords[0],y+ycoords[4])
self.draw_line(x + xcoords[0], y+ycoords[4], x+xcoords[3],y+ycoords[2])
self.draw_line(x + xcoords[3], y+ycoords[2], x+xcoords[1],y+ycoords[0])
self.draw_line(x + xcoords[3], y+ycoords[2], x+xcoords[3],y+ycoords[0])
self.draw_line(x + xcoords[0], y+ycoords[4], x+xcoords[3],y+ycoords[0])
# Draw rightmost triangle shape
self.draw_line(x + xcoords[2], y+ycoords[5], x+xcoords[5],y+ycoords[3])
self.draw_line(x + xcoords[5], y+ycoords[3], x+xcoords[4],y+ycoords[7])
self.draw_line(x + xcoords[4], y+ycoords[7], x+xcoords[2],y+ycoords[5])
self.draw_line(x + xcoords[2], y+ycoords[5], x+xcoords[2],y+ycoords[7])
self.draw_line(x + xcoords[2], y+ycoords[7], x+xcoords[5],y+ycoords[3])
self.draw_line(x + xcoords[2], y+ycoords[5], x+xcoords[3],y+ycoords[2])
# Draw the cube sides
self.draw_line(x + xcoords[1], y+ycoords[0], x+xcoords[0],y+ycoords[1])
self.draw_line(x + xcoords[5], y+ycoords[0], x+xcoords[4],y+ycoords[1])
self.draw_line(x + xcoords[1], y+ycoords[6], x+xcoords[0],y+ycoords[7])
self.draw_line(x + xcoords[5], y+ycoords[6], x+xcoords[4],y+ycoords[7])
# Draw the front face
self.draw_line(x + xcoords[0], y+ycoords[1], x+xcoords[4],y+ycoords[1])
self.draw_line(x + xcoords[4], y+ycoords[1], x+xcoords[4],y+ycoords[7])
self.draw_line(x + xcoords[4], y+ycoords[7], x+xcoords[0],y+ycoords[7])
self.draw_line(x + xcoords[0], y+ycoords[7], x+xcoords[0],y+ycoords[1])
self.pop_line_color()
def render_canvas(self) -> None:
""" Draws the image to the canvas """
#self.draw_pbm(0, 0, "imgs/pyramids.ppm")
if self.img == 1:
self.push_line_color(Style(color="white", bold=True))
self.draw_pbm(0, 0, "imgs/Chess_Stereogram.ppm")
self.pop_line_color()
elif self.img == 0:
self.push_line_color(Style(color="white", bold=True))
self.draw_pbm(0, 0, "imgs/stereo.pbm")
self.pop_line_color()
elif self.img == 2:
self.draw_cube_shape(40, 10, self.xcoords_left, self.ycoords)
self.draw_cube_shape(40 + int(1.6 * self.cube_width), 10, self.xcoords_right, self.ycoords)
class Slideshow:
def __init__(self, parent):
self.parent = parent
async def __call__(self) -> None:
if self.parent._timer is not None:
if self.parent.which_img == len(self.parent.images)-1:
self.parent.which_img = 0
else:
self.parent.which_img += 1
await self.parent._parent.post_message(RenderableUpdate(self))
# ===================================================
# A class to render the "Graphics" tab images
# ===================================================
class PBMSlideshow(TuiPlot):
def __init__(
self,
style: StyleType = None,
name: str | None = None,
width: int | None = None,
height: int | None = None,
):
""" Initialize a Graphics graph with image rending test routines """
super().__init__(style, name, width, height, border=False)
self.absolute_coords = True
self._slideshow_timer = None
self.images = ["mona_lisa.ppm","pyramids.ppm","Einstein.ppm","Marilyn.ppm","wizard.ppm", "sombrero.ppm" ]
self.which_img = 0
self.slideshow = Slideshow(self)
def render_canvas(self) -> None:
""" Draws the image to the canvas """
if self._slideshow_timer is None:
self._slideshow_timer = Timer(
self._parent,
2,
self._parent,
name="Animator",
callback=self.slideshow,
pause=True,
)
self._slideshow_timer.start()
else:
self._slideshow_timer.resume()
self.push_line_color(Style(color="white", bold=True))
#self.draw_image(0, 0, "imgs/squares.jpg")
#self.draw_image(0, 0, "imgs/Marilyn.ppm")
#self.draw_image(0, 0, "imgs/mona_lisa.ppm")
#self.draw_image(0, 0, "imgs/wizard.ppm")
self.draw_image(0, 0, f"imgs/{self.images[self.which_img]}")
self.pop_line_color()
# ===================================================
# A class to render the "Graphics" tab images
# ===================================================
class Graphics(TuiPlot):
def __init__(
self,
style: StyleType = None,
name: str | None = None,
width: int | None = None,
height: int | None = None,
):
""" Initialize a Graphics graph with image rending test routines """
super().__init__(style, name, width, height, border=False)
self.absolute_coords = True
def draw_eye(self, x, y, block_chars = False) -> None:
""" Test suite for draw_circle and draw_line """
# Draw the eyeball (3 concenric circles with different radius and color)
scale = 0.75
if block_chars:
self.push_block_chars()
self.draw_circle(x, y, int(30*scale), filled=True, color=Style(color="white",bold=True))
self.draw_circle(x, y, int(20*scale), filled=True, color="bright_blue")
# NOTE: Erase() is defined as Style(conceal=True) which is a color that
# erases dots (sets them to zero) vs. drawing with a background color
self.draw_circle(x, y, int(12*scale), filled=True, color=Erase())
self.draw_line(x-5, y-9, x-3, y-12)
if block_chars:
self.pop_block_chars()
# Draw the eyelashes
self.push_line_color(Style(color="white",bold=True))
self.draw_line(x-2, y-2, x-1, y-3)
for angle in range(-50, 51, 10):
a = angle + 90
#x1 = x + math.cos(a/360*2*math.pi) * .25 / 2
#y1 = y + math.sin(a/360*2*math.pi) * .25
#x2 = x + math.cos(a/360*2*math.pi) * .3 / 2
#y2 = y + math.sin(a/360*2*math.pi) * .3
x1 = x + math.cos(a/360*2*math.pi) * 55 / 2
y1 = y - math.sin(a/360*2*math.pi) * 25
x2 = x + math.cos(a/360*2*math.pi) * 65 / 2
y2 = y - math.sin(a/360*2*math.pi) * 30
self.draw_line(x1, y1, x2, y2)
self.pop_line_color()
def draw_flame(self, x, y):
""" Draws the candle flame """
self.push_line_color(Style(color="bright_yellow",bold=True))
self.draw_line(x+2, y-7, x+2, y-12)
self.draw_line(x+3, y-3, x+3, y-15)
self.draw_line(x+4, y-6, x+4, y-18)
self.draw_line(x+5, y-9, x+5, y-20)
self.draw_line(x+6, y-6, x+6, y-18)
self.draw_line(x+7, y-2, x+7, y-15)
self.draw_line(x+8, y-5, x+8, y-10)
self.pop_line_color()
# Draw the wick
self.draw_line(x+5, y, x+5, y-5, color="bright_black")
def render_canvas(self) -> None:
""" Draws a candel and two eyes as a unittest """
self.draw_eye(30, self.plot_height/2)
self.draw_eye(80, self.plot_height/2)
self.draw_circle(55, self.plot_height/2+30, 8, color="bright_red")
# Draw a candle
candle_y = self.plot_height/2-23
self.push_block_chars()
self.draw_flame(115, candle_y-2)
self.draw_rect(116, candle_y, 8, 50, filled=True, color=Style(color="navajo_white3"))
self.pop_block_chars()
self.draw_eye(156, self.plot_height/2, True)
self.draw_eye(206, self.plot_height/2, True)
self.push_block_chars()
self.draw_circle(180, self.plot_height/2+30, 8, color="bright_red")
self.pop_block_chars()
class TimePlot(TuiPlot):
def render_canvas(self) -> None:
""" Draws the a simple sine wave to the bare canvas """
self.push_line_color("bright_yellow")
lastx = 0
lasty = math.sin(0) + 0.5
for x in range(4, self.width*2, 4):
xf = x / (self.width*2)
waves = 3
y = math.sin(xf * waves * 2 * math.pi) / 2 + .5
if xf != lastx:
self.draw_line(lastx, lasty, xf, y)
lastx = xf
lasty = y
self.pop_line_color()
class FftPlot(TuiPlot):
def __init__(
self,
style: StyleType = None,
name: str | None = None,
width: int | None = None,
height: int | None = None,
):
""" Initialize an FFT graph with FFT data from a file """
super().__init__(style, name, width, height, border=False)
self.f_list = []
with open("fft_data/fft1.txt", "r") as file1:
self.f_list.append([float(line) for line in file1][8:])
with open("fft_data/fft2.txt", "r") as file1:
self.f_list.append([float(line) for line in file1][8:])
with open("fft_data/fft3.txt", "r") as file1:
self.f_list.append([float(line) for line in file1][8:])
with open("fft_data/fft4.txt", "r") as file1:
self.f_list.append([float(line) for line in file1][8:])
self.which_fft = 0
self._timer = None
self.add_x_label(Text("Frequency (GHz)",style=Style(color="blue")))
self.add_x_axis(PlotAxis(0, 8, 9, ".1f"), style=Style(color="navajo_white1"))
self.add_y_label(Text("Amplitude / dBM",style=Style(color="yellow")))
self.add_y_axis(PlotAxis(0, -120, 7, ".0f"), style=Style(color="navajo_white1"))
self.add_annotation(560, -10, Text("ENOB: 8.2",style=Style(color="bright_yellow")))
self.add_annotation(560, -15, Text("SNR: 48.7",style=Style(color="bright_yellow")))
self.add_title(Text("ADC 1 - 12 Bit",style=Style(color="bright_blue")))
def render_canvas(self) -> None:
if self._timer is None:
frames_per_second = 4
self._timer = Timer(
self._parent,
1 / frames_per_second,
self._parent,
name="Animator",
callback=self,
pause=True,
)
self._timer.start()
else:
self._timer.start()
""" Draws the FFT to the bare canvas """
fft = self.f_list[self.which_fft]
# Set the graph X and Y extents
self.set_y_extents(-120, 0)
self.set_x_extents(0, len(fft))
#self.push_line_color("grey74")
self.push_line_color("grey50")
lastx = 0
lasty = fft[0]
x = 0
harmonic_bins = [860, 1290, 1760]
spur_bins = [610, 1040, 1160]
signal_bin = 420
pop_bins = [460]
pop_bins.extend([b+35 for b in harmonic_bins])
pop_bins.extend([b+35 for b in spur_bins])
for f in fft:
if x == 420:
self.push_line_color(Style(color="bright_green",bold=True))
elif x in pop_bins:
self.pop_line_color()
elif x in harmonic_bins:
self.push_line_color(Style(color="bright_blue",bold=True))
elif x in spur_bins:
self.push_line_color(Style(color="bright_red",bold=True))
self.draw_line(lastx, lasty, x, f)
lastx = x
lasty = f
x += 1
self.pop_line_color()
async def __call__(self) -> None:
if self._timer is not None:
if self.which_fft == 3:
self.which_fft = 0
else:
self.which_fft += 1
await self._parent.post_message(RenderableUpdate(self))
class SampleAppHeader(Header):
def render(self) -> RenderableType:
""" Create a custom header with cool emojis """
header_table = Table.grid(padding=(0, 1), expand=True)
header_table.style = self.style
header_table.add_column(justify="left", ratio=0, width=8)
header_table.add_column("title", justify="center", ratio=1)
header_table.add_column("clock", justify="right", width=8)
header_table.add_row(
"❄🔥", self.full_title, self.get_clock() if self.clock else ""
)
header: RenderableType
header = Panel(header_table, style=self.style) if self.tall else header_table
return header
class Controls(ControlPanelView, can_focus=True):
"""Controls for Sample TUI."""
def on_mount(self) -> None:
"""Event when widget is first mounted (added to a parent view)."""
# Create drop lists
self.drop_radiocolors = Droplist(
"radiocolors",
"blue,red,white,yellow",
select="blue",
max_height=9)
self.drop_radiotype = Droplist(
"radiotype",
"large,small,ascii,pointer",
select="large",
max_height=5)
self.drop_checktype = Droplist(
"checktype",
"large,medium,small,cross,ascii",
select="large",
max_height=6)
# Create example controls
radio_type = "large"
radio_color = "blue"
checkbox_type = "large"
self.samp_radio_group = RadioGroup("Option 1,Option 2,Option 3", indent=2,radiotype=radio_type, radiocolor=radio_color)
self.samp_radio = {
'option_1' : self.samp_radio_group.get_button('option_1'),
'option_2' : self.samp_radio_group.get_button('option_2'),
'option_3' : self.samp_radio_group.get_button('option_3'),
}
self.samp_radio['option_1'].selected = True
self.samp_retain = Checkbutton("Retain", indent=2, checktype=checkbox_type )
self.edit1 = FixedTitleTextInput(title="Edit 1", value="-20")
self.edit2 = FixedTitleTextInput(title="Edit 2", value="100")
self.edit3 = FixedTitleTextInput(title="Edit 3", value="20")
self.edit4 = FixedTitleTextInput(title="Edit 4", value="8")
# Create checkbox controls
self.checks = {}
self.checks['chk1'] = Checkbutton("Check 1", indent=2, checktype=checkbox_type )
self.checks['chk2'] = Checkbutton("Check 2", indent=2, checktype=checkbox_type )
self.checks['chk3'] = Checkbutton("Check 3", indent=2, checktype=checkbox_type )
self.checks['chk4'] = Checkbutton("Check 4", indent=2, checktype=checkbox_type )
self.checks['chk1'].checked = True
self.pause_on_error = Checkbutton("Pause on error", indent=1, top_margin=1, checktype=checkbox_type )
# Reset and start/stop buttons
self.reset_enable = Checkbutton("Enable reset", name="enable_reset", indent=1, checktype=checkbox_type )
self.reset_button = HoverButton("Reset", width=7, style="white on red", visible=False, indent=4)
self.start_stop = HoverButton("Start", name='start', width=7, indent = 4)
self.pause_button = HoverButton("Pause", name='pause', visible=False, indent = 4)
self.running = False
self.paused = False
self.status_label = Label("")
# Create the control panel header labels
HEADER = "white on dark_sea_green4"
control_panel_label = Label("Control Panel", style=HEADER, align="center")
self.panel = [
# The top label that spans both columns. NOTE: Column spans can be added seperately also (see below)
{'row': "name='title',size=1,spacer=1,span='col1|col2'", 'controls' : [ control_panel_label ] },
# Actual controls
{'row': "name='rtype',size=1,spacer=1", 'controls' : [ Label("Radio Type"), self.drop_radiotype ] },
{'row': "name='rcolor',size=1,spacer=1", 'controls' : [ Label("Radio Color"), self.drop_radiocolors ] },
{'row': "name='order',size=1,spacer=1", 'controls' : [ Label("Check Type"), self.drop_checktype ] },
{'row': "name='ctrl1',size=1,repeat=5,spacer=1", 'controls' : [ Label("Sample Radio"), Label("Sample Checkbox") ] },
{ 'controls' : [ self.samp_radio['option_1'], self.checks['chk1'] ] },
{ 'controls' : [ self.samp_radio['option_2'], self.checks['chk2'] ] },
{ 'controls' : [ self.samp_radio['option_3'], self.checks['chk3'] ] },
{ 'controls' : [ Label(""), self.checks['chk4'] ] },
{'row': "name='edit',size=3,repeat=2,spacer=1", 'controls' : [ self.edit1, self.edit2 ] },
{ 'controls' : [ self.edit3, self.pause_on_error ] },
{'row': "name='reset',size=1,spacer=1", 'controls' : [ self.reset_enable, self.reset_button ] },
{'row': "name='run1',size=1,spacer=2", 'controls' : [ self.start_stop, self.pause_button ] },
{'row': "name='status',size=1", 'controls' : [ self.status_label ] },
]
# Create columns and rows
self.grid.add_column("col1", max_size=17)
self.grid.add_column("col2", max_size=32)
self.grid.set_gutter(1)
# This is an alternate way of adding column spans if not in-line above
# self.grid.add_col_spans(
# clear="col1-start|col2-end,title",
# )
# Add rows and controls to the control panel grid
for r in self.panel:
if 'row' in r:
eval(f'self.grid.add_row({r["row"]})')
self.grid.place(*r['controls'])
async def handle_button_pressed(self, message: ButtonPressed) -> None:
"""A message sent by the button widget"""
global ctrl_table
assert isinstance(message.sender, Button)
button_name = message.sender.name
if button_name == 'radiotype':
radiotype = self.drop_radiotype.selected
for r in self.samp_radio:
self.samp_radio[r].radiotype = radiotype
# Update the table in the Controls tab
await ctrl_table.update_row(
0,
*[None, self.drop_radiotype.selected,
Radiobutton("Modified",radiotype=radiotype,radiocolor="blue",selected=True)]
)
elif button_name == 'radiocolors':
color = self.drop_radiocolors.selected
for r in self.samp_radio:
self.samp_radio[r].radiocolor = color
# Update the table in the Controls tab
await ctrl_table.update_row(
1,
*[None, self.drop_radiocolors.selected,
Radiobutton("Modified",radiotype="large",radiocolor=color,selected=True)]
)
if button_name == 'checktype':
checktype = self.drop_checktype.selected
for r in self.checks:
self.checks[r].checktype = checktype
self.pause_on_error.checktype = checktype
self.reset_enable.checktype = checktype
# Update the table in the Controls tab
await ctrl_table.update_row(
2,
*[None, self.drop_checktype.selected,
Checkbutton("Modified",checktype=checktype,checked=True)]
)
elif button_name == 'start':
if not self.running:
self.running = True
self.start_stop.label = "Stop"
self.status_label.label = "Running sweep..."
self.pause_button.visible = True
else:
self.running = False
self.paused = False
self.start_stop.label = "Start"
self.pause_button.label = "Pause"
self.status_label.label = ""
self.pause_button.visible = False
elif button_name == 'pause':
if self.running:
if self.paused:
self.paused = False
self.pause_button.label = "Pause"
self.status_label.label = "Running sweep..."
else:
self.paused = True
self.pause_button.label = "Resume"
self.status_label.label = "Sweep paused"
elif button_name == "enable_reset":
if self.reset_enable.checked:
self.reset_button.visible = True
else:
self.reset_button.visible = False
# ========================================================================
# This is a super simple CLI command processor that somewhat mimics
# the Python REPL
# ========================================================================
async def process_command(command, width) -> str:
"""Process a command from the CliInput"""
global tabs
console_width = get_console().width
get_console().width = width - 4
# Test if assigning a variable
process_command_var = ''
process_command_expression = ''
for xxxxx1 in range(len(command)):
if command[xxxxx1] == '=':
# Process the command as an assignment
process_command_var=command[:xxxxx1].strip()
process_command_expression = command[xxxxx1+1:].strip()
# Add command to our history window
hist_cmd = command.replace('"[', '"\\[').replace("'[","'\\[")
cmd = "\n" + hist_cmd
if not tabs.has_tab("history"):
tabs.add_tab("history", "Command History", True)
tabs.select_tab("history")
tabs.add_renderable("history", cmd, False)
try:
f = io.StringIO()
with redirect_stdout(f):
globals()[process_command_var] = eval(process_command_expression)
return
except Exception as e:
resp = 'ERROR: ' + str(e)
break
elif command[xxxxx1] == '(' or command[xxxxx1] == '"':
break
if process_command_var == '':
try:
f = io.StringIO()
with redirect_stdout(f):
eval(command)
resp = f.getvalue()[:-1]
except Exception as e:
resp = 'ERROR: ' + str(e)
get_console().width = console_width
# Add command to our history window
hist_cmd = command.replace('"[', '"\\[').replace("'[","'\\[")
cmd = "\n" + hist_cmd+"\n" + resp
if not tabs.has_tab("history"):
tabs.add_tab("history", "Command History", True)
tabs.select_tab("history")
tabs.add_renderable("history", cmd, False)
return resp
class MyApp(App):
"""Override of Textual App refresh to work with Linux 'screen' program"""
def refresh(self, repaint: bool = True, layout: bool = False) -> None:
sync_available = os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal"
if not self._closed:
console = self.console
try:
if sync_available:
console.file.write("\x1bP=1s\x1b\\")
console.print(Screen(Control.home(), self.view, Control.home()))
# Disable these two lines for screen
# if sync_available:
# console.file.write("\x1bP=2s\x1b\\")
console.file.flush()
except Exception:
self.panic()
async def on_load(self) -> None:
"""Sent before going in to application mode."""
# Bind our basic keys
await self.bind("b", "view.toggle('controls')", "Toggle controls")
await self.bind("ctrl+d", "quit", "Quit")
await self.bind("ctrl+n", "app.next_stereo()", "Next Stereograph")
# Get path to show
try:
self.path = sys.argv[1]
except IndexError:
self.path = os.path.abspath(
os.path.join(os.path.basename(__file__), "../../")
)
async def next_stereo(self) -> None:
self.stereo.next()
async def on_mount(self) -> None:
"""Call after terminal goes in to application mode"""
global cmdHistory
global cmdText
global tabs
global ctrl_table
cli_height = 10
# Create our tabs
self.body = Tabs(border_style=Style(color="blue"), border_focus_style=Style(color="green"))
tabs = self.body
self.body.add_tab("history", "Command History", True)
self.body.add_tab("controls", "Controls")
self.body.add_tab("graph", "Time Plot")
self.body.add_tab("fft", "FFT Plot")
self.body.add_tab("graphics", "Graphics")
self.body.add_tab("pbm", "PBM")
self.body.add_tab("3d", "Stereograph")
# Add content to the command history tab
cmdText = "[yellow]Command History\n\n[white]This is an example of a Dynamic Widget that displays text which can be changed dynamically.\n\n"
cmdText += 'Try a command in the Command Window something like:\n x=Panel.fit("\[on red]Hello World",style="on blue")\n'
cmdText += ' print(x)\n inspect(x)'
cmdHistory = Dynamic(cmdText,name="history")
self.body.add_renderable("history", cmdHistory, True)
# Add content to the controls tab
self.ctrl_table = DynamicTable("controls")
ctrl_table = self.ctrl_table
self.ctrl_table.add_column("Control")
self.ctrl_table.add_column("Value")
self.ctrl_table.add_column("Sample")
self.ctrl_table.add_row(*["Radio Type", "large", Radiobutton("Example",radiotype="large",selected=True)])
self.ctrl_table.add_row(*["Radio Color", "blue", Radiobutton("Example",radiotype="large",radiocolor="blue",selected=True)])
self.ctrl_table.add_row(*["Check Type", "large", Checkbutton("Example",checktype="large",checked=True)])
self.body.add_renderable("controls", Dynamic('Below is a "DynamicTable" that updates when the Droplist controls (left) update.\n',name="table"))
self.body.add_renderable("controls", self.ctrl_table)
self.stereo = PBMStereo(name="3d",style=Style(color="grey63"))
self.body.add_renderable("graph", Dynamic("Time Domain Plot",name="time"))
self.body.add_renderable("graph", TimePlot(name="graph",style=Style(color="grey63")))
self.body.add_renderable("fft", FftPlot(name="fft",style=Style(color="grey63")))
self.body.add_renderable("graphics", Graphics(name="graphics",style=Style(color="grey63")))
self.body.add_renderable("pbm", PBMSlideshow(name="pbm",style=Style(color="grey63")))
self.body.add_renderable("3d", self.stereo)
# Create the bottom Command Line Interface (CLI) and the control panel
self.cmd = CliInput(process_command, name="cli", title="Command Window", height=cli_height)
self.controls = Controls(name='Control Panel')
# Dock our widgets
await self.view.dock(SampleAppHeader(), edge="top")
await self.view.dock(Footer(), edge="bottom")
await self.view.dock(self.cmd, edge="bottom", size=cli_height, name="cmd")
# Note the directory is also in a scroll view
await self.view.dock(self.controls, edge="left", size=40, name="controls")
await self.view.dock(self.body, edge="top")
if __name__ == "__main__":
# Run our app class
app = MyApp(driver_class=ScreenDriver)
app.run(title="Sample TUI",driver=ScreenDriver)