From 9287c9e5d46f035e8e4f2c2ff731434b1d13c53f Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Mon, 21 Sep 2020 14:26:02 +0100 Subject: [PATCH 01/16] Update README --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b529ad8..d9a69e9 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,59 @@ This is a project to build an easy-to-use GUI toolkit using just the windows con Eventually I aim to mirror the placement methods available in Tkinter such as Pack, Place, and Grid. I would also like to make a version of CMDUI for ANSI code driven terminals in the same easy-to-use style, although in the meantime CMDUI is only runnable in the windows console (or CMD). -## Current Issues +## Installation +Clone the repo, download as a zip file, or use the following command... +```sh +pip install CMDUI +``` + +## Example Usage - Stop Watch +A simple stopwatch UI. This example demonstrates the use of buttons, labels, and text variables. +```python +import CMDUI as CMD +import threading +import time + + +def counter(): + btn_txt.set("Stop") + + tt = time.time() + while running: + lab_txt.set(f"{time.time()-tt:.2f}") + time.sleep(0.01) + + btn_txt.set("Reset") + + +def stopwatch(): + if btn_txt.get() == "Reset": + btn_txt.set("Start") + lab_txt.set("") + return + + global running + running = not running + threading.Thread(target=counter).start() + +cmdui = CMD.CMDUI() +running = False + +lab_txt = CMD.StringVar() +btn_txt = CMD.StringVar() +btn_txt.set("Start") + +lab = CMD.Label(cmdui, textvariable=lab_txt) +lab.pack() + +but = CMD.Button(cmdui, textvariable=btn_txt, command=stopwatch) +but.pack() + +cmdui.mainloop() +``` + +## Current Issues Due to the way lines of text are wrapped in CMD the GUI had a tendency to explode with artifacts all over the place when the window is resized. The temporary fix I have implemented for this is very unstable and can cause crashes occasionally but I am currently still looking into a better fix for this problem. A means of toggling "Wrap text output on resize" really needs to be implemented in the C/C++ bindings for windows and subsequently ctypes or win32console for this problem to be fixed completely. \ No newline at end of file From 3035084bcb2a9e34cb09621e8199da6c870b02df Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Sat, 26 Sep 2020 00:47:19 +0100 Subject: [PATCH 02/16] New 'colors' module to handle win console colours --- CMDUI/colors.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 CMDUI/colors.py diff --git a/CMDUI/colors.py b/CMDUI/colors.py new file mode 100644 index 0000000..be66591 --- /dev/null +++ b/CMDUI/colors.py @@ -0,0 +1,61 @@ +_colors = { + "black" : 0x0, + "blue" : 0x1, + "green" : 0x2, + "aqua" : 0x3, + "red" : 0x4, + "purple" : 0x5, + "yellow" : 0x6, + "white" : 0x7, + "grey" : 0x8, + "light_blue" : 0x9, + "light_green" : 0xa, + "light_aqua" : 0xb, + "light_red" : 0xc, + "light_purple" : 0xd, + "light_yellow" : 0xe, + "bright_white" : 0xf, +} + + +def _sanitize_color_name(color_name: str) -> str: + assert isinstance(color_name, str) + return color_name.lower().strip().replace(" ", "_") + + +def get_bg_code(color_name: str) -> int: + color_name = _sanitize_color_name(color_name) + return _colors[color_name] * 16 + + +def get_fg_code(color_name: str) -> int: + color_name = _sanitize_color_name(color_name) + return _colors[color_name] + + +def get_color(fg_color_name: str, bg_color_name: str = "") -> int: + fg = get_fg_code(fg_color_name) + + if len(bg_color_name): + bg = get_bg_code(bg_color_name) + return fg | bg + else: + return fg + + +def get_color_hex(fg_color_name: str, bg_color_name: str) -> hex: + return hex(get_color(fg_color_name, bg_color_name)) + + +""" Really quick example usage... + +import os, time + +col_code = get_color('Light green', 'Red') +hex_col_code = get_color_hex('Light green', 'Red') + +print(col_code) +print(hex_col_code) + +os.system(f"color {hex_col_code[2:]}") +""" \ No newline at end of file From 759bd125026e8f46d57b9386b57dcb5af0953de3 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Sun, 27 Sep 2020 15:51:32 +0100 Subject: [PATCH 03/16] Implementing frames and refactor pack method (1/2) --- CMDUI/CMDUI.py | 287 +++++++++++++++--- CMDUI/console/__init__.py | 0 CMDUI/{ => console}/consolemanager.py | 139 +++++---- .../resizelistener.py} | 2 +- examples/cmdui_example.py | 27 +- examples/oop_example.py | 25 ++ 6 files changed, 366 insertions(+), 114 deletions(-) create mode 100644 CMDUI/console/__init__.py rename CMDUI/{ => console}/consolemanager.py (90%) rename CMDUI/{utils/windowresizelistener.py => console/resizelistener.py} (97%) create mode 100644 examples/oop_example.py diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index d8ce53b..faa2439 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -1,5 +1,6 @@ -from CMDUI.consolemanager import ConsoleManager +from CMDUI.console.consolemanager import ConsoleManager from CMDUI.variables import StringVar, IntVar, DoubleVar, BooleanVar +from CMDUI.colors import get_color import math @@ -11,9 +12,6 @@ def __init__(self): on_click=self.on_mouse_click, on_resize=self.on_window_resize) - self.frames = [] - - self.widgets = [] self.packed_widgets = [] self.console_manager.init_console_output() @@ -39,18 +37,21 @@ def mainloop(self): def on_mouse_move(self, x, y): for widget in self.packed_widgets: - widget.check_hover(x, y) + if isinstance(widget, Widget): + widget.check_hover(x, y) def on_mouse_click(self, x, y, button_state): if button_state == 1: for widget in self.packed_widgets: - if widget.check_inside(x, y): - widget.on_press(x, y) + if isinstance(widget, Widget): + if widget.check_inside(x, y): + widget.on_press(x, y) elif button_state == 0: for widget in self.packed_widgets: - widget.on_release() + if isinstance(widget, Widget): + widget.on_release() def on_window_resize(self): @@ -59,60 +60,270 @@ def on_window_resize(self): self.update_pack(undraw=False) - def update_pack(self, undraw=True): - widget_total_space = sum((widget.height for widget in self.packed_widgets)) - widgets_start = math.floor((self.window_height / 2) - (widget_total_space / 2)) + def pack_widget(self, widget): + if widget in self.packed_widgets: + return False + self.packed_widgets.append(widget) + + + def update_pack(self, undraw=False): + # https://www.tcl.tk/man/tcl8.6/TkCmd/pack.htm + + # If expand=True... + # height_of_widgets = sum(widget.height for widget in self.packed_widgets) + # self.y = math.floor((self.height / 2) - (height_of_widgets / 2)) + + packing_list = self.packed_widgets + + cavaty = [0, 0, self.window_width, self.window_height] + + + for widget in packing_list: + + parcel = [0, 0, 0, 0] + + # 1 Parcel allocation... + if widget.pack_options["side"] == "top": + parcel[0] = cavaty[0] + parcel[1] = cavaty[1] + parcel[2] = cavaty[2] + parcel[3] = widget.height + + elif widget.pack_options["side"] == "bottom": + parcel[0] = cavaty[0] + parcel[1] = (cavaty[1] + cavaty[3]) - widget.height + parcel[2] = cavaty[2] + parcel[3] = widget.height + + elif widget.pack_options["side"] == "left": + parcel[0] = cavaty[0] + parcel[1] = cavaty[1] + parcel[2] = widget.width + parcel[3] = cavaty[3] + + elif widget.pack_options["side"] == "right": + parcel[0] = (cavaty[0] + cavaty[2]) - widget.width + parcel[1] = cavaty[1] + parcel[2] = widget.width + parcel[3] = cavaty[3] + + import time + import random + x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] + h = int(f"0x{str(random.choice(x))}f", 16) + self.console_manager.color_area(parcel[0], parcel[1], parcel[2], parcel[3], h) + time.sleep(0.5) + + # Extra for CMDUI... + if undraw: + widget.undraw() + + # 2 Slave dimensions... + if widget.pack_options["fill"] == "both" or widget.pack_options["fill"] == "x": + widget.width = parcel[2] + + if widget.pack_options["fill"] == "both" or widget.pack_options["fill"] == "y": + widget.height = parcel[3] + + time.sleep(0.5) + + # 3 Slave positioning... + if widget.pack_options["side"] == "top": + widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] + widget.y = parcel[1] + + cavaty[1] += parcel[3] + cavaty[3] -= parcel[3] + + elif widget.pack_options["side"] == "bottom": + widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] + widget.y = parcel[1] + + cavaty[3] -= parcel[3] + + elif widget.pack_options["side"] == "left": + widget.x = parcel[0] + widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] + + cavaty[0] += parcel[2] + cavaty[2] -= parcel[2] + + elif widget.pack_options["side"] == "right": + widget.x = parcel[0] + widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] + + cavaty[2] -= parcel[2] + + widget.draw() + + return False + + num_expanded = 0 + for widget in self.packed_widgets: + if widget.pack_options["expand"]: + num_expanded += 1 + + if num_expanded > 0: + height_of_widgets = sum(widget.height for widget in self.packed_widgets) + leftover_space = self.window_height - height_of_widgets + expandable_space = leftover_space // num_expanded + widget_yoffset = 0 for widget in self.packed_widgets: - + + if widget.pack_options["expand"]: + widget_yoffset += expandable_space//2 + if undraw: widget.undraw() widget.x = math.floor((self.window_width / 2) - (widget.width / 2)) - widget.y = widgets_start+widget_yoffset + widget.y = 0 + widget_yoffset + + if isinstance(widget, Frame): + widget_yoffset = widget.update_pack() widget.draw() widget_yoffset += widget.height + if widget.pack_options["expand"]: + widget_yoffset += expandable_space//2 + class Frame: - def __init__(self, cmdui_obj, x=0, y=0): - cmdui_obj.frames.append(self) - self.cmdui_obj = cmdui_obj + def __init__(self, parent, x=0, y=0): + if isinstance(parent, CMDUI): + self.cmdui_obj = parent + elif isinstance(parent, Frame): + self.cmdui_obj = parent.cmdui_obj - self.widgets = [] + self.parent = parent + + self.frames = [] + + self.packed_widgets = [] self.x = x self.y = y self.width = 0 self.height = 0 + self.pack_options = { + "expand": False, + "side": "top", + "fill":"None" + } -class Widget: + + def pack(self): + if self not in self.cmdui_obj.packed_widgets: + self.cmdui_obj.packed_widgets.append(self) - def __init__(self, cmdui_obj, x=0, y=0): - cmdui_obj.widgets.append(self) + def pack_widget(self, widget): + if widget in self.packed_widgets: + return False + + self.packed_widgets.append(widget) + self.cmdui_obj.packed_widgets.append(widget) + self.calc_frame_size() + + + def calc_frame_size(self): + self.height = sum(widget.height for widget in self.packed_widgets) + try: + self.width = max(widget.width for widget in self.packed_widgets) + except ValueError: + assert len(self.packed_widgets) == 0 + + print(self.x, self.y, self.width, self.height) + + + def draw(self): + import random + x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] + h = int(f"0x{str(random.choice(x))}f", 16) + print(h) + self.paint_background(h) + + + def undraw(self): + pass + + + def update_pack(self, undraw=False): + # If expand=True... + # height_of_widgets = sum(widget.height for widget in self.packed_widgets) + # self.y = math.floor((self.height / 2) - (height_of_widgets / 2)) + + widget_yoffset = 0 + + for widget in self.packed_widgets: + + if undraw: + widget.undraw() + + widget.x = math.floor((self.x + self.width / 2) - (widget.width / 2)) + widget.y = self.y + widget_yoffset + + if isinstance(widget, Frame): + widget_yoffset = widget.update_pack() + + widget.draw() + + widget_yoffset += widget.height - if isinstance(cmdui_obj, CMDUI): - self.cmdui_obj = cmdui_obj - elif isinstance(cmdui_obj, Frame): - self.cmdui_obj = cmdui_obj.cmdui_obj + return widget_yoffset + + def paint_background(self, color): + self.cmdui_obj.console_manager.color_area( + self.x, + self.y, + self.width, + self.height, + color + ) + + + +class Widget: + + + def __init__(self, parent, x=0, y=0): + if isinstance(parent, CMDUI): + self.cmdui_obj = parent + elif isinstance(parent, Frame): + self.cmdui_obj = parent.cmdui_obj + + self.parent = parent self.x = x self.y = y self.display = "" + self.pack_options = { + "expand": False, + "side": "top", + "fill":"None" + } + - def pack(self): - if self not in self.cmdui_obj.packed_widgets: - self.cmdui_obj.packed_widgets.append(self) + def pack(self, expand=False, side="top", fill="none"): + self.parent.pack_widget(self) + + assert isinstance(expand, bool), "Parameter 'expand' must be a boolean!" + assert side in ("top", "bottom", "left", "right"), "Parameter 'side' must one of the following 'top', 'bottom', 'left', or 'right'." + assert fill in ("none", "both", "x", "y"), "Parameter 'fill' must one of the following 'none', 'both', 'x', or 'y'." + + self.pack_options["expand"] = expand + self.pack_options["side"] = side + self.pack_options["fill"] = fill def draw(self): @@ -257,15 +468,18 @@ def draw_hover(self): def draw_pressed(self): FOREGROUND_INTENSITY = 0x0008 - cur_color = self.cmdui_obj.console_manager.get_color() + cur_color = self.cmdui_obj.console_manager.get_console_color_code() # Bitwise XOR using FOREGROUND_INTENSITY; Keeps the colour, inverts the intensity. - self.cmdui_obj.console_manager.set_color(cur_color ^ FOREGROUND_INTENSITY) - - self.display = self.generate_active_display() - self.draw() - - self.cmdui_obj.console_manager.set_color(cur_color) + select_color = cur_color ^ FOREGROUND_INTENSITY + + self.cmdui_obj.console_manager.color_area( + self.x, + self.y, + self.width, + self.height, + select_color + ) def check_hover(self, x, y): @@ -335,11 +549,10 @@ def draw_hover(self): def draw_pressed(self): - FOREGROUND_INTENSITY = 0x0008 - - cur_color = self.cmdui_obj.console_manager.get_color() + cur_color = self.cmdui_obj.console_manager.get_console_color_code() # Bitwise XOR using FOREGROUND_INTENSITY; Keeps the colour, inverts the intensity. + FOREGROUND_INTENSITY = 0x0008 self.cmdui_obj.console_manager.set_color(cur_color ^ FOREGROUND_INTENSITY) self.draw() diff --git a/CMDUI/console/__init__.py b/CMDUI/console/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/CMDUI/consolemanager.py b/CMDUI/console/consolemanager.py similarity index 90% rename from CMDUI/consolemanager.py rename to CMDUI/console/consolemanager.py index fd6f4c5..65dba36 100644 --- a/CMDUI/consolemanager.py +++ b/CMDUI/console/consolemanager.py @@ -1,5 +1,5 @@ from CMDUI.utils.stoppablethread import StoppableThread -from CMDUI.utils.windowresizelistener import ResizeListener +from CMDUI.console.resizelistener import ResizeListener import win32console import pywintypes import win32file @@ -26,47 +26,6 @@ def __init__(self, on_move=None, on_click=None, on_scroll=None, on_resize=None): self._res_c = 0 - def get_color(self): - console_screen_info = self.console_output.GetConsoleScreenBufferInfo() - return console_screen_info["Attributes"] - - - def set_color(self, color): - self.console_output.SetConsoleTextAttribute(color) - - - def set_cursor_visable(self, visable): - console_cursor_info = self.console_output.GetConsoleCursorInfo() - self.console_output.SetConsoleCursorInfo(console_cursor_info[0], visable) - - - def print(self, text): - if not self._res_c: - self.console_output.WriteConsole(f'{text}\n') - - - def print_pos(self, x, y, text): - if not self._res_c: - self._print_pos(x, y, text) - - - def _print_pos(self, x, y, text): - pos = win32console.PyCOORDType(x, y) - try: - with self._thread_lock: - self.console_output.SetConsoleCursorPosition(pos) - self.console_output.WriteConsole(text) - except pywintypes.error: - pass - - - def color_pos(self, x, y, color): - pos = win32console.PyCOORDType(x, y) - attr=self.console_output.ReadConsoleOutputAttribute(Length=1, ReadCoord=pos)[0] - new_attr=(attr&~color)|win32console.BACKGROUND_BLUE - self.console_output.WriteConsoleOutputAttribute((new_attr,), pos) - - def init_console_input(self): """Create a new console input buffer, disable quickedit mode and enable mouse and window input.""" @@ -180,24 +139,15 @@ def _event_loop(self): #self.console_output.WriteConsole(str(input_record)) time.sleep(0.01) - - def on_move(self, x, y): - pass - - - def on_click(self, x, y, button_state): - pass - - - def on_scroll(self, x, y): - pass - - def on_resize(self): - pass + def on_window_resize(self): + self._res_c += 1 + if self._res_c == 1: + t = threading.Thread(target=self.currently_resizing_thread) + t.start() - def checker(self): + def currently_resizing_thread(self): res_n = 0 self.window_width = self.console_size[0] @@ -216,13 +166,6 @@ def checker(self): self.on_resize() - def on_window_resize(self): - self._res_c += 1 - if self._res_c == 1: - t = threading.Thread(target=self.checker) - t.start() - - def set_buffersize_to_windowsize(self): buffinfo = self.console_output.GetConsoleScreenBufferInfo() windowinfo = buffinfo['Window'] @@ -231,10 +174,7 @@ def set_buffersize_to_windowsize(self): sizex = windowinfo.Right - windowinfo.Left + 1 sizey = windowinfo.Bottom - windowinfo.Top + 1 - try: - self.console_output.SetConsoleScreenBufferSize(win32console.PyCOORDType(sizex, sizey)) - except pywintypes.error: - pass + self.console_output.SetConsoleScreenBufferSize(win32console.PyCOORDType(sizex, sizey)) def color_flip_fun(self, pos): @@ -264,6 +204,65 @@ def get_virtual_keys(self): return vkeys + def set_cursor_visable(self, visable): + console_cursor_info = self.console_output.GetConsoleCursorInfo() + self.console_output.SetConsoleCursorInfo(console_cursor_info[0], visable) + + + def print(self, text): + if not self._res_c: + self.console_output.WriteConsole(f'{text}\n') + + + def print_pos(self, x, y, text): + if not self._res_c: + self._print_pos(x, y, text) + + + def _print_pos(self, x, y, text): + pos = win32console.PyCOORDType(x, y) + with self._thread_lock: + self.console_output.SetConsoleCursorPosition(pos) + self.console_output.WriteConsole(text) + + + def get_console_color_code(self): + console_screen_info = self.console_output.GetConsoleScreenBufferInfo() + return console_screen_info["Attributes"] + + + def set_color(self, color): + self.console_output.SetConsoleTextAttribute(color) + + + def color_pos(self, x, y, color): + pos = win32console.PyCOORDType(x, y) + self.console_output.WriteConsoleOutputAttribute((color,), pos) + + + def color_area(self, x, y, w, h, color): + attrs = (color,) * w + for i in range(h): + pos = win32console.PyCOORDType(x, y+i) + self.console_output.WriteConsoleOutputAttribute(attrs, pos) + + + def on_move(self, x, y): + pass + + + def on_click(self, x, y, button_state): + pass + + + def on_scroll(self, x, y): + pass + + + def on_resize(self): + pass + + @property def console_size(self): buffinfo = self.console_output.GetConsoleScreenBufferInfo() @@ -283,8 +282,6 @@ def free_console(self): free_console=True try: win32console.AllocConsole() - except Exception as exc: - if exc.winerror != 5: - raise + except: free_console=False return free_console \ No newline at end of file diff --git a/CMDUI/utils/windowresizelistener.py b/CMDUI/console/resizelistener.py similarity index 97% rename from CMDUI/utils/windowresizelistener.py rename to CMDUI/console/resizelistener.py index 002b520..66abbba 100644 --- a/CMDUI/utils/windowresizelistener.py +++ b/CMDUI/console/resizelistener.py @@ -1,4 +1,4 @@ -from .stoppablethread import StoppableThread +from CMDUI.utils.stoppablethread import StoppableThread import ctypes import ctypes.wintypes diff --git a/examples/cmdui_example.py b/examples/cmdui_example.py index 5a81a63..1c964c9 100644 --- a/examples/cmdui_example.py +++ b/examples/cmdui_example.py @@ -30,16 +30,33 @@ def stopwatch(): running = False -cmdui = CMD.CMDUI() +root = CMD.CMDUI() txt = CMD.StringVar() btn_txt = CMD.StringVar() btn_txt.set("Start") -lab = CMD.Label(cmdui, textvariable=txt) -lab.pack() +# lab = CMD.Label(root, textvariable=txt) +# lab.pack() -but = CMD.Button(cmdui, textvariable=btn_txt, command=stopwatch) +# frm = CMD.Frame(root) +# frm.pack() + +# but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) +# but.pack() + + +lab = CMD.Label(root, textvariable=txt) +lab.pack(side="bottom") + +but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) +but.pack(side="right") + +but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) but.pack() -cmdui.mainloop() \ No newline at end of file +but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) +but.pack(side="left") + + +root.mainloop() \ No newline at end of file diff --git a/examples/oop_example.py b/examples/oop_example.py new file mode 100644 index 0000000..558f47f --- /dev/null +++ b/examples/oop_example.py @@ -0,0 +1,25 @@ +import CMDUI as CMD + +class Application(CMD.Frame): + def __init__(self, master=None): + super().__init__(master) + self.master = master + self.pack() + self.create_widgets() + + def create_widgets(self): + self.hi_there = CMD.Button(self) + self.hi_there["text"] = "Hello World\n(click me)" + self.hi_there["command"] = self.say_hi + self.hi_there.pack(side="top") + + self.quit = CMD.Button(self, text="QUIT", fg="red", + command=self.master.destroy) + self.quit.pack(side="bottom") + + def say_hi(self): + print("hi there, everyone!") + +root = CMD.CMDUI() +app = Application(master=root) +app.mainloop() \ No newline at end of file From 30c28530d0a435e6a6f3f38b372ce7847b7d7c56 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Tue, 29 Sep 2020 16:14:28 +0100 Subject: [PATCH 04/16] Improvements to pack algorithm --- CMDUI/CMDUI.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index faa2439..484bd93 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -80,6 +80,9 @@ def update_pack(self, undraw=False): for widget in packing_list: + if cavaty[2] <= 0 or cavaty[3] <= 0: + return + parcel = [0, 0, 0, 0] # 1 Parcel allocation... @@ -87,32 +90,32 @@ def update_pack(self, undraw=False): parcel[0] = cavaty[0] parcel[1] = cavaty[1] parcel[2] = cavaty[2] - parcel[3] = widget.height + parcel[3] = widget.height if widget.height <= cavaty[3] else cavaty[3] elif widget.pack_options["side"] == "bottom": parcel[0] = cavaty[0] - parcel[1] = (cavaty[1] + cavaty[3]) - widget.height + parcel[1] = (cavaty[1] + cavaty[3]) - widget.height if widget.height <= cavaty[3] else cavaty[1] parcel[2] = cavaty[2] - parcel[3] = widget.height + parcel[3] = widget.height if widget.height <= cavaty[3] else cavaty[3] elif widget.pack_options["side"] == "left": parcel[0] = cavaty[0] parcel[1] = cavaty[1] - parcel[2] = widget.width + parcel[2] = widget.width if widget.width <= cavaty[2] else cavaty[2] parcel[3] = cavaty[3] elif widget.pack_options["side"] == "right": - parcel[0] = (cavaty[0] + cavaty[2]) - widget.width + parcel[0] = (cavaty[0] + cavaty[2]) - widget.width if widget.width <= cavaty[2] else cavaty[0] parcel[1] = cavaty[1] - parcel[2] = widget.width + parcel[2] = widget.width if widget.width <= cavaty[2] else cavaty[2] parcel[3] = cavaty[3] import time import random - x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] + x = ["a","c","d","1","2","3","4","5","6","7","8","9"] h = int(f"0x{str(random.choice(x))}f", 16) self.console_manager.color_area(parcel[0], parcel[1], parcel[2], parcel[3], h) - time.sleep(0.5) + time.sleep(0.1) # Extra for CMDUI... if undraw: @@ -125,34 +128,37 @@ def update_pack(self, undraw=False): if widget.pack_options["fill"] == "both" or widget.pack_options["fill"] == "y": widget.height = parcel[3] - time.sleep(0.5) - # 3 Slave positioning... if widget.pack_options["side"] == "top": - widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] + widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] if widget.width <= parcel[2] else parcel[0] widget.y = parcel[1] cavaty[1] += parcel[3] cavaty[3] -= parcel[3] elif widget.pack_options["side"] == "bottom": - widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] + widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] if widget.width <= parcel[2] else parcel[0] widget.y = parcel[1] cavaty[3] -= parcel[3] elif widget.pack_options["side"] == "left": widget.x = parcel[0] - widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] + widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] if widget.height <= parcel[3] else parcel[1] cavaty[0] += parcel[2] cavaty[2] -= parcel[2] elif widget.pack_options["side"] == "right": widget.x = parcel[0] - widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] + widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] if widget.height <= parcel[3] else parcel[1] - cavaty[2] -= parcel[2] + cavaty[2] -= parcel[2] + + + if parcel[2] < widget.width or parcel[3] < widget.height: + # If the widget is too big for the parcel then dont pack it... + continue widget.draw() From c8e6ae6da22a722d64a3f5235bc20de995e93df3 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Tue, 20 Oct 2020 23:43:38 +0100 Subject: [PATCH 05/16] Implementing the new pack method --- CMDUI/CMDUI.py | 232 ++++++++++++++++++++------------------ examples/cmdui_example.py | 3 + 2 files changed, 127 insertions(+), 108 deletions(-) diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index 484bd93..ecbeb74 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -69,134 +69,150 @@ def pack_widget(self, widget): def update_pack(self, undraw=False): # https://www.tcl.tk/man/tcl8.6/TkCmd/pack.htm - # If expand=True... - # height_of_widgets = sum(widget.height for widget in self.packed_widgets) - # self.y = math.floor((self.height / 2) - (height_of_widgets / 2)) - - packing_list = self.packed_widgets - - cavaty = [0, 0, self.window_width, self.window_height] - + # PASS #1 - for widget in packing_list: + width = max_width = self.window_width + height = max_height = self.window_height - if cavaty[2] <= 0 or cavaty[3] <= 0: - return - - parcel = [0, 0, 0, 0] - - # 1 Parcel allocation... - if widget.pack_options["side"] == "top": - parcel[0] = cavaty[0] - parcel[1] = cavaty[1] - parcel[2] = cavaty[2] - parcel[3] = widget.height if widget.height <= cavaty[3] else cavaty[3] + for widget in self.packed_widgets: + if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": + tmp = widget.width + if tmp > max_width: + max_width = tmp + height += widget.height + else: + tmp = widget.height + if tmp > max_height: + max_height = tmp + width += widget.width + + if width > max_width: + max_width = width + if height > max_height: + max_height = height + + if max_width < self.window_width: + max_width = self.window_width + if max_height < self.window_height: + max_height = self.window_height + + # If window size already changed then just stop and try again in a mo... + if max_width == self.window_width or max_height == self.window_height: + self.update_pack() + + # PASS #2 + + cavity_x = 0 + cavity_y = 0 + + cavity_width = self.window_width + cavity_height = self.window_height + + for widget_num, widget in enumerate(self.packed_widgets): + if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": + frame_width = cavity_width + frame_height = widget.height + if widget.pack_options["expand"]: + frame_height += self.y_expansion(widget_num, cavity_height) + + cavity_height -= frame_height + if cavity_height < 0: + frame_height += cavity_height + cavity_height = 0 + + frame_x = cavity_x + if widget.pack_options["side"] == "top": + frame_y = cavity_y + cavity_y += frame_height + else: + frame_y = cavity_y + cavity_height + else: + frame_height = cavity_height + frame_width = widget.width + if widget.pack_options["expand"]: + frame_width += self.x_expansion(widget_num, cavity_width) + + cavity_width -= frame_width + if cavity_width < 0: + frame_width += cavity_width + cavity_width = 0 + + frame_y = cavity_y + if widget.pack_options["side"] == "top": + frame_x = cavity_x + cavity_x += frame_width + else: + frame_x = cavity_x + cavity_width - elif widget.pack_options["side"] == "bottom": - parcel[0] = cavaty[0] - parcel[1] = (cavaty[1] + cavaty[3]) - widget.height if widget.height <= cavaty[3] else cavaty[1] - parcel[2] = cavaty[2] - parcel[3] = widget.height if widget.height <= cavaty[3] else cavaty[3] - elif widget.pack_options["side"] == "left": - parcel[0] = cavaty[0] - parcel[1] = cavaty[1] - parcel[2] = widget.width if widget.width <= cavaty[2] else cavaty[2] - parcel[3] = cavaty[3] - - elif widget.pack_options["side"] == "right": - parcel[0] = (cavaty[0] + cavaty[2]) - widget.width if widget.width <= cavaty[2] else cavaty[0] - parcel[1] = cavaty[1] - parcel[2] = widget.width if widget.width <= cavaty[2] else cavaty[2] - parcel[3] = cavaty[3] - + # Extra for CMDUI... import time import random x = ["a","c","d","1","2","3","4","5","6","7","8","9"] h = int(f"0x{str(random.choice(x))}f", 16) - self.console_manager.color_area(parcel[0], parcel[1], parcel[2], parcel[3], h) + self.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) time.sleep(0.1) - - # Extra for CMDUI... if undraw: widget.undraw() - # 2 Slave dimensions... - if widget.pack_options["fill"] == "both" or widget.pack_options["fill"] == "x": - widget.width = parcel[2] - - if widget.pack_options["fill"] == "both" or widget.pack_options["fill"] == "y": - widget.height = parcel[3] - - # 3 Slave positioning... - if widget.pack_options["side"] == "top": - widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] if widget.width <= parcel[2] else parcel[0] - widget.y = parcel[1] + if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": + widget.x = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x + widget.y = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y + else: + widget.x = frame_x + widget.y = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y - cavaty[1] += parcel[3] - cavaty[3] -= parcel[3] - - elif widget.pack_options["side"] == "bottom": - widget.x = math.floor((parcel[2] / 2) - (widget.width / 2)) + parcel[0] if widget.width <= parcel[2] else parcel[0] - widget.y = parcel[1] - - cavaty[3] -= parcel[3] + widget.draw() - elif widget.pack_options["side"] == "left": - widget.x = parcel[0] - widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] if widget.height <= parcel[3] else parcel[1] - cavaty[0] += parcel[2] - cavaty[2] -= parcel[2] + def x_expansion(self, widget_num, cavity_width): + minExpand = cavity_width + num_expand = 0 + for widget_n in range(widget_num, len(self.packed_widgets)): + widget = self.packed_widgets[widget_n] + child_width = widget.width - elif widget.pack_options["side"] == "right": - widget.x = parcel[0] - widget.y = math.floor((parcel[3] / 2) - (widget.height / 2)) + parcel[1] if widget.height <= parcel[3] else parcel[1] - - cavaty[2] -= parcel[2] + if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": + if num_expand: + cur_expand = (cavity_width - child_width) / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + else: + cavity_width -= child_width + if widget.pack_options["expand"]: + num_expand += 1 + if num_expand: + cur_expand = cavity_width / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + + return int(minExpand) if not (minExpand < 0) else 0 - if parcel[2] < widget.width or parcel[3] < widget.height: - # If the widget is too big for the parcel then dont pack it... - continue - widget.draw() + def y_expansion(self, widget_num, cavity_height): + minExpand = cavity_height + num_expand = 0 + for widget_n in range(widget_num, len(self.packed_widgets)): + widget = self.packed_widgets[widget_n] + child_height = widget.height - return False - - num_expanded = 0 - for widget in self.packed_widgets: - if widget.pack_options["expand"]: - num_expanded += 1 - - if num_expanded > 0: - height_of_widgets = sum(widget.height for widget in self.packed_widgets) - leftover_space = self.window_height - height_of_widgets - expandable_space = leftover_space // num_expanded - - - widget_yoffset = 0 - - for widget in self.packed_widgets: - - if widget.pack_options["expand"]: - widget_yoffset += expandable_space//2 - - if undraw: - widget.undraw() - - widget.x = math.floor((self.window_width / 2) - (widget.width / 2)) - widget.y = 0 + widget_yoffset - - if isinstance(widget, Frame): - widget_yoffset = widget.update_pack() - - widget.draw() - - widget_yoffset += widget.height - - if widget.pack_options["expand"]: - widget_yoffset += expandable_space//2 + if widget.pack_options["side"] == "left" or widget.pack_options["side"] == "right": + if num_expand: + cur_expand = (cavity_height - child_height) / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + else: + cavity_height -= child_height + if widget.pack_options["expand"]: + num_expand += 1 + + if num_expand: + cur_expand = cavity_height / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + + return int(minExpand) if not (minExpand < 0) else 0 class Frame: diff --git a/examples/cmdui_example.py b/examples/cmdui_example.py index 1c964c9..84fb717 100644 --- a/examples/cmdui_example.py +++ b/examples/cmdui_example.py @@ -49,6 +49,9 @@ def stopwatch(): lab = CMD.Label(root, textvariable=txt) lab.pack(side="bottom") +but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) +but.pack(side="top", expand=True) + but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) but.pack(side="right") From 202a5932e98c731081187b5ba2035fcbf5ea75b5 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Wed, 21 Oct 2020 00:06:01 +0100 Subject: [PATCH 06/16] Pack method bugfixes --- .gitignore | 5 ++++- CMDUI/CMDUI.py | 15 ++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index dd9f4e9..699d890 100644 --- a/.gitignore +++ b/.gitignore @@ -99,4 +99,7 @@ dmypy.json .pytype/ # Debugging files -*.bat \ No newline at end of file +*.bat + +# IDE config files +.vscode \ No newline at end of file diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index ecbeb74..78c2443 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -138,7 +138,7 @@ def update_pack(self, undraw=False): cavity_width = 0 frame_y = cavity_y - if widget.pack_options["side"] == "top": + if widget.pack_options["side"] == "left": frame_x = cavity_x cavity_x += frame_width else: @@ -152,15 +152,12 @@ def update_pack(self, undraw=False): h = int(f"0x{str(random.choice(x))}f", 16) self.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) time.sleep(0.1) + if undraw: widget.undraw() - if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": - widget.x = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x - widget.y = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y - else: - widget.x = frame_x - widget.y = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y + widget.x = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x + widget.y = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y widget.draw() @@ -417,7 +414,7 @@ def update_text(self, text): if pk_update_needed: self.undraw() - self.x = math.floor((self.cmdui_obj.window_width / 2) - (self.width / 2)) + #self.x = math.floor((self.cmdui_obj.window_width / 2) - (self.width / 2)) self.draw() @@ -464,7 +461,7 @@ def update_text(self, text): if pk_update_needed: self.undraw() - self.x = math.floor((self.cmdui_obj.window_width / 2) - (self.width / 2)) + # self.x = math.floor((self.cmdui_obj.window_width / 2) - (self.width / 2)) self.draw() From 7eea3542a17d4d0a454955aa45bd375d25e2ebe8 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Wed, 21 Oct 2020 23:41:50 +0100 Subject: [PATCH 07/16] Converting to frame based geometry --- CMDUI/CMDUI.py | 94 ++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index 78c2443..e445704 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -16,8 +16,13 @@ def __init__(self): self.console_manager.init_console_output() - self.window_width = self.console_manager.console_size[0] - self.window_height = self.console_manager.console_size[1] + self.main_frame = Frame( + self, + x=0, + y=0, + width=self.console_manager.console_size[0], + height=self.console_manager.console_size[1] + ) def mainloop(self): @@ -55,8 +60,8 @@ def on_mouse_click(self, x, y, button_state): def on_window_resize(self): - self.window_width = self.console_manager.console_size[0] - self.window_height = self.console_manager.console_size[1] + self.main_frame.width = self.console_manager.console_size[0] + self.main_frame.height = self.console_manager.console_size[1] self.update_pack(undraw=False) @@ -66,16 +71,19 @@ def pack_widget(self, widget): self.packed_widgets.append(widget) - def update_pack(self, undraw=False): + def update_pack(self, cur_frame=False, undraw=False): # https://www.tcl.tk/man/tcl8.6/TkCmd/pack.htm + if not cur_frame: + cur_frame = self.main_frame + # PASS #1 - width = max_width = self.window_width - height = max_height = self.window_height + width = max_width = cur_frame.width + height = max_height = cur_frame.height for widget in self.packed_widgets: - if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": + if widget.side == "top" or widget.side == "bottom": tmp = widget.width if tmp > max_width: max_width = tmp @@ -91,13 +99,13 @@ def update_pack(self, undraw=False): if height > max_height: max_height = height - if max_width < self.window_width: - max_width = self.window_width - if max_height < self.window_height: - max_height = self.window_height + if max_width < cur_frame.width: + max_width = cur_frame.width + if max_height < cur_frame.height: + max_height = cur_frame.height # If window size already changed then just stop and try again in a mo... - if max_width == self.window_width or max_height == self.window_height: + if max_width == cur_frame.width or max_height == cur_frame.height: self.update_pack() # PASS #2 @@ -105,14 +113,14 @@ def update_pack(self, undraw=False): cavity_x = 0 cavity_y = 0 - cavity_width = self.window_width - cavity_height = self.window_height + cavity_width = cur_frame.width + cavity_height = cur_frame.height for widget_num, widget in enumerate(self.packed_widgets): - if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": + if widget.side == "top" or widget.side == "bottom": frame_width = cavity_width frame_height = widget.height - if widget.pack_options["expand"]: + if widget.expand: frame_height += self.y_expansion(widget_num, cavity_height) cavity_height -= frame_height @@ -121,7 +129,7 @@ def update_pack(self, undraw=False): cavity_height = 0 frame_x = cavity_x - if widget.pack_options["side"] == "top": + if widget.side == "top": frame_y = cavity_y cavity_y += frame_height else: @@ -129,7 +137,7 @@ def update_pack(self, undraw=False): else: frame_height = cavity_height frame_width = widget.width - if widget.pack_options["expand"]: + if widget.expand: frame_width += self.x_expansion(widget_num, cavity_width) cavity_width -= frame_width @@ -138,7 +146,7 @@ def update_pack(self, undraw=False): cavity_width = 0 frame_y = cavity_y - if widget.pack_options["side"] == "left": + if widget.side == "left": frame_x = cavity_x cavity_x += frame_width else: @@ -151,7 +159,7 @@ def update_pack(self, undraw=False): x = ["a","c","d","1","2","3","4","5","6","7","8","9"] h = int(f"0x{str(random.choice(x))}f", 16) self.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) - time.sleep(0.1) + time.sleep(0.07) if undraw: widget.undraw() @@ -169,14 +177,14 @@ def x_expansion(self, widget_num, cavity_width): widget = self.packed_widgets[widget_n] child_width = widget.width - if widget.pack_options["side"] == "top" or widget.pack_options["side"] == "bottom": + if widget.side == "top" or widget.side == "bottom": if num_expand: cur_expand = (cavity_width - child_width) / num_expand if cur_expand < minExpand: minExpand = cur_expand else: cavity_width -= child_width - if widget.pack_options["expand"]: + if widget.expand: num_expand += 1 if num_expand: @@ -194,14 +202,14 @@ def y_expansion(self, widget_num, cavity_height): widget = self.packed_widgets[widget_n] child_height = widget.height - if widget.pack_options["side"] == "left" or widget.pack_options["side"] == "right": + if widget.side == "left" or widget.side == "right": if num_expand: cur_expand = (cavity_height - child_height) / num_expand if cur_expand < minExpand: minExpand = cur_expand else: cavity_height -= child_height - if widget.pack_options["expand"]: + if widget.expand: num_expand += 1 if num_expand: @@ -215,7 +223,7 @@ def y_expansion(self, widget_num, cavity_height): class Frame: - def __init__(self, parent, x=0, y=0): + def __init__(self, parent, x=0, y=0, width=0, height=0): if isinstance(parent, CMDUI): self.cmdui_obj = parent elif isinstance(parent, Frame): @@ -229,14 +237,12 @@ def __init__(self, parent, x=0, y=0): self.x = x self.y = y - self.width = 0 - self.height = 0 + self.width = width + self.height = height - self.pack_options = { - "expand": False, - "side": "top", - "fill":"None" - } + self.expand = False + self.side = "top" + self.fill = "none" def pack(self): @@ -326,11 +332,9 @@ def __init__(self, parent, x=0, y=0): self.display = "" - self.pack_options = { - "expand": False, - "side": "top", - "fill":"None" - } + self.expand = False + self.side = "top" + self.fill = "none" def pack(self, expand=False, side="top", fill="none"): @@ -340,9 +344,9 @@ def pack(self, expand=False, side="top", fill="none"): assert side in ("top", "bottom", "left", "right"), "Parameter 'side' must one of the following 'top', 'bottom', 'left', or 'right'." assert fill in ("none", "both", "x", "y"), "Parameter 'fill' must one of the following 'none', 'both', 'x', or 'y'." - self.pack_options["expand"] = expand - self.pack_options["side"] = side - self.pack_options["fill"] = fill + self.expand = expand + self.side = side + self.fill = fill def draw(self): @@ -414,7 +418,7 @@ def update_text(self, text): if pk_update_needed: self.undraw() - #self.x = math.floor((self.cmdui_obj.window_width / 2) - (self.width / 2)) + #self.x = math.floor((self.cmdui_obj.main_frame.width / 2) - (self.width / 2)) self.draw() @@ -461,7 +465,7 @@ def update_text(self, text): if pk_update_needed: self.undraw() - # self.x = math.floor((self.cmdui_obj.window_width / 2) - (self.width / 2)) + # self.x = math.floor((self.cmdui_obj.main_frame.width / 2) - (self.width / 2)) self.draw() @@ -658,7 +662,7 @@ class Menu(Widget): def __init__(self, cmdui_obj): super().__init__(cmdui_obj, x=0, y=0) - self.width = self.cmdui_obj.window_width + self.width = self.cmdui_obj.main_frame.width self.height = 2 self.options = [] @@ -679,7 +683,7 @@ def generate_menu(self): def draw(self): self.x = 0 self.y = 0 - self.width = self.cmdui_obj.window_width + self.width = self.cmdui_obj.main_frame.width self.display = self.generate_menu() for i in range(len(self.display)): From e1f84e5311de1f034d4f7753f9036b96a68dc55a Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Thu, 22 Oct 2020 00:36:07 +0100 Subject: [PATCH 08/16] Making widgets 're_pack' on content change --- CMDUI/CMDUI.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index e445704..8748973 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -33,7 +33,7 @@ def mainloop(self): # self.console_manager.set_cursor_visable(False) # Initially building widgets! - self.update_pack(undraw=False) + self.update_pack() self.console_manager.join() finally: @@ -62,7 +62,7 @@ def on_mouse_click(self, x, y, button_state): def on_window_resize(self): self.main_frame.width = self.console_manager.console_size[0] self.main_frame.height = self.console_manager.console_size[1] - self.update_pack(undraw=False) + self.update_pack() def pack_widget(self, widget): @@ -71,7 +71,7 @@ def pack_widget(self, widget): self.packed_widgets.append(widget) - def update_pack(self, cur_frame=False, undraw=False): + def update_pack(self, cur_frame=False): # https://www.tcl.tk/man/tcl8.6/TkCmd/pack.htm if not cur_frame: @@ -152,6 +152,7 @@ def update_pack(self, cur_frame=False, undraw=False): else: frame_x = cavity_x + cavity_width + widget.pack_frame = [frame_x, frame_y, frame_width, frame_height] # Extra for CMDUI... import time @@ -161,12 +162,14 @@ def update_pack(self, cur_frame=False, undraw=False): self.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) time.sleep(0.07) - if undraw: - widget.undraw() + new_wx = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x + new_wy = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y - widget.x = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x - widget.y = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y + if new_wx == widget.x and new_wy == widget.y: + return + widget.x = new_wx + widget.y = new_wy widget.draw() @@ -335,8 +338,9 @@ def __init__(self, parent, x=0, y=0): self.expand = False self.side = "top" self.fill = "none" + self.pack_frame = [0, 0, 0, 0] + - def pack(self, expand=False, side="top", fill="none"): self.parent.pack_widget(self) @@ -349,6 +353,18 @@ def pack(self, expand=False, side="top", fill="none"): self.fill = fill + def re_pack(self): + + # Need to check if the windows new position is too bit for the current frame! + + new_wx = math.floor((self.pack_frame[2] / 2) - (self.width / 2)) + self.pack_frame[0] if self.width <= self.pack_frame[2] else self.pack_frame[0] + new_wy = math.floor((self.pack_frame[3] / 2) - (self.height / 2)) + self.pack_frame[1] if self.height <= self.pack_frame[3] else self.pack_frame[1] + self.undraw() + self.x = new_wx + self.y = new_wy + self.draw() + + def draw(self): x_coord = self.x if self.x > 0 else 0 y_coord = self.y if self.y > 0 else 0 @@ -417,9 +433,7 @@ def update_text(self, text): self.display = self.generate_display() if pk_update_needed: - self.undraw() - #self.x = math.floor((self.cmdui_obj.main_frame.width / 2) - (self.width / 2)) - self.draw() + self.re_pack() def generate_display(self): @@ -464,9 +478,7 @@ def update_text(self, text): self.display = self.generate_display() if pk_update_needed: - self.undraw() - # self.x = math.floor((self.cmdui_obj.main_frame.width / 2) - (self.width / 2)) - self.draw() + self.re_pack() def generate_display(self): From 6e407dadfabf83f40de5915a13ad6a0eb6b97e67 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Fri, 6 Nov 2020 16:23:09 +0000 Subject: [PATCH 09/16] Updating url in setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f2c3173..492fdef 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ NAME = 'CMDUI' DESCRIPTION = 'A GUI toolkit for CMD.' -URL = 'https://github.com/TheBrokenEstate/CMDUI' +URL = 'https://github.com/danny-burrows/CMDUI' EMAIL = 'dannyburrows@protonmail.com' AUTHOR = 'Dan Burrows' REQUIRES_PYTHON = '>=3.6.0' From 77740699713e0c1f951333881201faaec2f0db15 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Fri, 6 Nov 2020 20:54:35 +0000 Subject: [PATCH 10/16] Moving CMDUI class to be a child of Frame --- CMDUI/CMDUI.py | 258 ++++++++++++++++++++++--------------------------- 1 file changed, 114 insertions(+), 144 deletions(-) diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index 8748973..e3cbdff 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -4,83 +4,84 @@ import math -class CMDUI: +class Frame: - - def __init__(self): - self.console_manager = ConsoleManager(on_move=self.on_mouse_move, - on_click=self.on_mouse_click, - on_resize=self.on_window_resize) - self.packed_widgets = [] - - self.console_manager.init_console_output() + def __init__(self, parent, x=0, y=0, width=0, height=0): + if isinstance(parent, CMDUI): + self.cmdui_obj = parent + elif isinstance(parent, Frame): + self.cmdui_obj = parent.cmdui_obj - self.main_frame = Frame( - self, - x=0, - y=0, - width=self.console_manager.console_size[0], - height=self.console_manager.console_size[1] - ) + self.parent = parent + self.frames = [] - def mainloop(self): - try: - self.console_manager.start() + self.packed_widgets = [] + + self.x = x + self.y = y + self.width = width + self.height = height - # (DISABLED FOR DEBUG) - # self.console_manager.set_cursor_visable(False) + self.expand = False + self.side = "top" + self.fill = "none" - # Initially building widgets! - self.update_pack() - self.console_manager.join() - finally: - self.console_manager.stop() + def pack(self): + if self not in self.cmdui_obj.packed_widgets: + self.cmdui_obj.packed_widgets.append(self) - def on_mouse_move(self, x, y): - for widget in self.packed_widgets: - if isinstance(widget, Widget): - widget.check_hover(x, y) + def pack_widget(self, widget): + if widget in self.packed_widgets: + return False + self.packed_widgets.append(widget) + self.cmdui_obj.packed_widgets.append(widget) + self.calc_frame_size() + - def on_mouse_click(self, x, y, button_state): - if button_state == 1: - for widget in self.packed_widgets: - if isinstance(widget, Widget): - if widget.check_inside(x, y): - widget.on_press(x, y) - - elif button_state == 0: - for widget in self.packed_widgets: - if isinstance(widget, Widget): - widget.on_release() + def calc_frame_size(self): + self.height = sum(widget.height for widget in self.packed_widgets) + try: + self.width = max(widget.width for widget in self.packed_widgets) + except ValueError: + assert len(self.packed_widgets) == 0 + + print(self.x, self.y, self.width, self.height) - def on_window_resize(self): - self.main_frame.width = self.console_manager.console_size[0] - self.main_frame.height = self.console_manager.console_size[1] - self.update_pack() + def draw(self): + import random + x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] + h = int(f"0x{str(random.choice(x))}f", 16) + print(h) + self.paint_background(h) - def pack_widget(self, widget): - if widget in self.packed_widgets: - return False - self.packed_widgets.append(widget) + def undraw(self): + pass + + def paint_background(self, color): + self.cmdui_obj.console_manager.color_area( + self.x, + self.y, + self.width, + self.height, + color + ) - def update_pack(self, cur_frame=False): + + def update_pack(self, force_draw=False): # https://www.tcl.tk/man/tcl8.6/TkCmd/pack.htm - if not cur_frame: - cur_frame = self.main_frame - # PASS #1 - width = max_width = cur_frame.width - height = max_height = cur_frame.height + width = max_width = self.width + height = max_height = self.height for widget in self.packed_widgets: if widget.side == "top" or widget.side == "bottom": @@ -99,22 +100,22 @@ def update_pack(self, cur_frame=False): if height > max_height: max_height = height - if max_width < cur_frame.width: - max_width = cur_frame.width - if max_height < cur_frame.height: - max_height = cur_frame.height + if max_width < self.width: + max_width = self.width + if max_height < self.height: + max_height = self.height # If window size already changed then just stop and try again in a mo... - if max_width == cur_frame.width or max_height == cur_frame.height: - self.update_pack() + #if max_width == self.width or max_height == self.height: + # self.update_pack() # PASS #2 cavity_x = 0 cavity_y = 0 - cavity_width = cur_frame.width - cavity_height = cur_frame.height + cavity_width = self.width + cavity_height = self.height for widget_num, widget in enumerate(self.packed_widgets): if widget.side == "top" or widget.side == "bottom": @@ -159,14 +160,14 @@ def update_pack(self, cur_frame=False): import random x = ["a","c","d","1","2","3","4","5","6","7","8","9"] h = int(f"0x{str(random.choice(x))}f", 16) - self.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) + self.cmdui_obj.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) time.sleep(0.07) new_wx = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x new_wy = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y - if new_wx == widget.x and new_wy == widget.y: - return + # if not force_draw and new_wx == widget.x and new_wy == widget.y: + # return widget.x = new_wx widget.y = new_wy @@ -223,101 +224,70 @@ def y_expansion(self, widget_num, cavity_height): return int(minExpand) if not (minExpand < 0) else 0 -class Frame: - - - def __init__(self, parent, x=0, y=0, width=0, height=0): - if isinstance(parent, CMDUI): - self.cmdui_obj = parent - elif isinstance(parent, Frame): - self.cmdui_obj = parent.cmdui_obj - - self.parent = parent - - self.frames = [] - - self.packed_widgets = [] - - self.x = x - self.y = y - self.width = width - self.height = height - - self.expand = False - self.side = "top" - self.fill = "none" - - - def pack(self): - if self not in self.cmdui_obj.packed_widgets: - self.cmdui_obj.packed_widgets.append(self) - - - def pack_widget(self, widget): - if widget in self.packed_widgets: - return False +class CMDUI(Frame): - self.packed_widgets.append(widget) - self.cmdui_obj.packed_widgets.append(widget) - self.calc_frame_size() + def __init__(self): + self.console_manager = ConsoleManager(on_move=self.on_mouse_move, + on_click=self.on_mouse_click, + on_resize=self.on_window_resize) - def calc_frame_size(self): - self.height = sum(widget.height for widget in self.packed_widgets) - try: - self.width = max(widget.width for widget in self.packed_widgets) - except ValueError: - assert len(self.packed_widgets) == 0 + self.console_manager.init_console_output() + + super().__init__( + self, + x=0, + y=0, + width=self.console_manager.console_size[0], + height=self.console_manager.console_size[1] + ) - print(self.x, self.y, self.width, self.height) - def draw(self): - import random - x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] - h = int(f"0x{str(random.choice(x))}f", 16) - print(h) - self.paint_background(h) - + def mainloop(self): + try: + self.console_manager.start() - def undraw(self): - pass + # (DISABLED FOR DEBUG) + # self.console_manager.set_cursor_visable(False) + # Initially building widgets! + self.update_pack() - def update_pack(self, undraw=False): - # If expand=True... - # height_of_widgets = sum(widget.height for widget in self.packed_widgets) - # self.y = math.floor((self.height / 2) - (height_of_widgets / 2)) + self.console_manager.join() + finally: + self.console_manager.stop() - widget_yoffset = 0 + def on_mouse_move(self, x, y): for widget in self.packed_widgets: - - if undraw: - widget.undraw() + if isinstance(widget, Widget): + widget.check_hover(x, y) - widget.x = math.floor((self.x + self.width / 2) - (widget.width / 2)) - widget.y = self.y + widget_yoffset - if isinstance(widget, Frame): - widget_yoffset = widget.update_pack() + def on_mouse_click(self, x, y, button_state): + if button_state == 1: + for widget in self.packed_widgets: + if isinstance(widget, Widget): + if widget.check_inside(x, y): + widget.on_press(x, y) + + elif button_state == 0: + for widget in self.packed_widgets: + if isinstance(widget, Widget): + widget.on_release() - widget.draw() - widget_yoffset += widget.height - - return widget_yoffset + def on_window_resize(self): + self.width = self.console_manager.console_size[0] + self.height = self.console_manager.console_size[1] + self.update_pack(force_draw=True) - - def paint_background(self, color): - self.cmdui_obj.console_manager.color_area( - self.x, - self.y, - self.width, - self.height, - color - ) - + + def pack_widget(self, widget): + if widget in self.packed_widgets: + return False + self.packed_widgets.append(widget) class Widget: From 033ad08bb22204504cf52274f6e9a4c2b038d19c Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Sat, 7 Nov 2020 01:05:11 +0000 Subject: [PATCH 11/16] Now expanding cavity from 0 when updating pack --- CMDUI/CMDUI.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index e3cbdff..0669959 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -80,17 +80,17 @@ def update_pack(self, force_draw=False): # PASS #1 - width = max_width = self.width - height = max_height = self.height + width = max_width = 0 + height = max_height = 0 for widget in self.packed_widgets: if widget.side == "top" or widget.side == "bottom": - tmp = widget.width + tmp = widget.width + width if tmp > max_width: max_width = tmp height += widget.height else: - tmp = widget.height + tmp = widget.height + height if tmp > max_height: max_height = tmp width += widget.width @@ -105,9 +105,14 @@ def update_pack(self, force_draw=False): if max_height < self.height: max_height = self.height + # Expand window or frame if required... + if max_width > self.width and max_height > self.height: + self.width = max_width + self.height = max_height + # If window size already changed then just stop and try again in a mo... - #if max_width == self.width or max_height == self.height: - # self.update_pack() + if max_width != self.width or max_height != self.height: + self.update_pack() # PASS #2 @@ -166,8 +171,8 @@ def update_pack(self, force_draw=False): new_wx = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x new_wy = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y - # if not force_draw and new_wx == widget.x and new_wy == widget.y: - # return + if not force_draw and new_wx == widget.x and new_wy == widget.y: + return widget.x = new_wx widget.y = new_wy @@ -414,6 +419,16 @@ def generate_display(self): return (top, mid, btm) + @property + def width(self): + return len(self.display[0]) + + + @property + def height(self): + return len(self.display) + + class Button(Widget): @@ -684,4 +699,3 @@ def __init__(self, menu_obj, text): menu_obj.options.append(self) self.text = text - From a1505314d5d406e3d2529c2618569f4036ee793c Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Sun, 8 Nov 2020 15:28:17 +0000 Subject: [PATCH 12/16] Post pack update code cleanup --- CMDUI/CMDUI.py | 144 ++++++++++++++------------------ CMDUI/console/consolemanager.py | 15 ++-- examples/cmdui_example.py | 12 +-- examples/frames_example.py | 63 ++++++++++++++ 4 files changed, 142 insertions(+), 92 deletions(-) create mode 100644 examples/frames_example.py diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index 0669959..af0a240 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -1,7 +1,10 @@ -from CMDUI.console.consolemanager import ConsoleManager -from CMDUI.variables import StringVar, IntVar, DoubleVar, BooleanVar -from CMDUI.colors import get_color import math +import random +import time + +from CMDUI.colors import get_color +from CMDUI.console.consolemanager import ConsoleManager +from CMDUI.variables import BooleanVar, DoubleVar, IntVar, StringVar class Frame: @@ -14,10 +17,7 @@ def __init__(self, parent, x=0, y=0, width=0, height=0): self.cmdui_obj = parent.cmdui_obj self.parent = parent - - self.frames = [] - - self.packed_widgets = [] + self.packed_items = [] self.x = x self.y = y @@ -29,35 +29,27 @@ def __init__(self, parent, x=0, y=0, width=0, height=0): self.fill = "none" - def pack(self): - if self not in self.cmdui_obj.packed_widgets: - self.cmdui_obj.packed_widgets.append(self) - - - def pack_widget(self, widget): - if widget in self.packed_widgets: + def pack(self, expand=False, side="top", fill="none"): + assert isinstance(expand, bool), \ + "Parameter 'expand' must be a boolean!" + assert side in ("top", "bottom", "left", "right"), \ + "Parameter 'side' must be one of the following 'top', 'bottom', 'left', or 'right'." + assert fill in ("none", "both", "x", "y"), \ + "Parameter 'fill' must be one of the following 'none', 'both', 'x', or 'y'." + + if self in self.parent.packed_items: return False - - self.packed_widgets.append(widget) - self.cmdui_obj.packed_widgets.append(widget) - self.calc_frame_size() - - - def calc_frame_size(self): - self.height = sum(widget.height for widget in self.packed_widgets) - try: - self.width = max(widget.width for widget in self.packed_widgets) - except ValueError: - assert len(self.packed_widgets) == 0 + self.parent.packed_items.append(self) + self.cmdui_obj.visable_widgets.append(self) - print(self.x, self.y, self.width, self.height) + self.expand = expand + self.side = side + self.fill = fill def draw(self): - import random x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] - h = int(f"0x{str(random.choice(x))}f", 16) - print(h) + h = int(f"0x{random.choice(x)}f", 16) self.paint_background(h) @@ -83,7 +75,7 @@ def update_pack(self, force_draw=False): width = max_width = 0 height = max_height = 0 - for widget in self.packed_widgets: + for widget in self.packed_items: if widget.side == "top" or widget.side == "bottom": tmp = widget.width + width if tmp > max_width: @@ -100,29 +92,25 @@ def update_pack(self, force_draw=False): if height > max_height: max_height = height - if max_width < self.width: - max_width = self.width - if max_height < self.height: - max_height = self.height - # Expand window or frame if required... - if max_width > self.width and max_height > self.height: + if max_width > self.width: self.width = max_width + if max_height > self.height: self.height = max_height # If window size already changed then just stop and try again in a mo... - if max_width != self.width or max_height != self.height: - self.update_pack() + # if max_width != self.width or max_height != self.height: + # self.update_pack() # PASS #2 - cavity_x = 0 - cavity_y = 0 + cavity_x = self.x + cavity_y = self.y cavity_width = self.width cavity_height = self.height - for widget_num, widget in enumerate(self.packed_widgets): + for widget_num, widget in enumerate(self.packed_items): if widget.side == "top" or widget.side == "bottom": frame_width = cavity_width frame_height = widget.height @@ -161,12 +149,10 @@ def update_pack(self, force_draw=False): widget.pack_frame = [frame_x, frame_y, frame_width, frame_height] # Extra for CMDUI... - import time - import random x = ["a","c","d","1","2","3","4","5","6","7","8","9"] - h = int(f"0x{str(random.choice(x))}f", 16) + h = int(f"0x{random.choice(x)}f", 16) self.cmdui_obj.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) - time.sleep(0.07) + #time.sleep(0.7) new_wx = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x new_wy = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y @@ -176,14 +162,17 @@ def update_pack(self, force_draw=False): widget.x = new_wx widget.y = new_wy + widget.draw() + if isinstance(widget, Frame): + widget.update_pack(force_draw=True) def x_expansion(self, widget_num, cavity_width): minExpand = cavity_width num_expand = 0 - for widget_n in range(widget_num, len(self.packed_widgets)): - widget = self.packed_widgets[widget_n] + for widget_n in range(widget_num, len(self.packed_items)): + widget = self.packed_items[widget_n] child_width = widget.width if widget.side == "top" or widget.side == "bottom": @@ -207,8 +196,8 @@ def x_expansion(self, widget_num, cavity_width): def y_expansion(self, widget_num, cavity_height): minExpand = cavity_height num_expand = 0 - for widget_n in range(widget_num, len(self.packed_widgets)): - widget = self.packed_widgets[widget_n] + for widget_n in range(widget_num, len(self.packed_items)): + widget = self.packed_items[widget_n] child_height = widget.height if widget.side == "left" or widget.side == "right": @@ -233,9 +222,11 @@ class CMDUI(Frame): def __init__(self): - self.console_manager = ConsoleManager(on_move=self.on_mouse_move, - on_click=self.on_mouse_click, - on_resize=self.on_window_resize) + self.console_manager = ConsoleManager( + on_move=self.on_mouse_move, + on_click=self.on_mouse_click, + on_resize=self.on_window_resize + ) self.console_manager.init_console_output() @@ -246,7 +237,7 @@ def __init__(self): width=self.console_manager.console_size[0], height=self.console_manager.console_size[1] ) - + self.visable_widgets = [] def mainloop(self): @@ -265,20 +256,20 @@ def mainloop(self): def on_mouse_move(self, x, y): - for widget in self.packed_widgets: + for widget in self.visable_widgets: if isinstance(widget, Widget): widget.check_hover(x, y) def on_mouse_click(self, x, y, button_state): if button_state == 1: - for widget in self.packed_widgets: + for widget in self.visable_widgets: if isinstance(widget, Widget): if widget.check_inside(x, y): widget.on_press(x, y) elif button_state == 0: - for widget in self.packed_widgets: + for widget in self.visable_widgets: if isinstance(widget, Widget): widget.on_release() @@ -289,12 +280,6 @@ def on_window_resize(self): self.update_pack(force_draw=True) - def pack_widget(self, widget): - if widget in self.packed_widgets: - return False - self.packed_widgets.append(widget) - - class Widget: @@ -317,12 +302,18 @@ def __init__(self, parent, x=0, y=0): def pack(self, expand=False, side="top", fill="none"): - self.parent.pack_widget(self) - - assert isinstance(expand, bool), "Parameter 'expand' must be a boolean!" - assert side in ("top", "bottom", "left", "right"), "Parameter 'side' must one of the following 'top', 'bottom', 'left', or 'right'." - assert fill in ("none", "both", "x", "y"), "Parameter 'fill' must one of the following 'none', 'both', 'x', or 'y'." - + assert isinstance(expand, bool), \ + "Parameter 'expand' must be a boolean!" + assert side in ("top", "bottom", "left", "right"), \ + "Parameter 'side' must be one of the following 'top', 'bottom', 'left', or 'right'." + assert fill in ("none", "both", "x", "y"), \ + "Parameter 'fill' must be one of the following 'none', 'both', 'x', or 'y'." + + if self in self.parent.packed_items: + return False + self.parent.packed_items.append(self) + self.cmdui_obj.visable_widgets.append(self) + self.expand = expand self.side = side self.fill = fill @@ -330,7 +321,10 @@ def pack(self, expand=False, side="top", fill="none"): def re_pack(self): - # Need to check if the windows new position is too bit for the current frame! + # Need to check if the windows new position is too big for the current frame! + if self.width > (self.pack_frame[2]-self.pack_frame[0]) or self.height > (self.pack_frame[3]-self.pack_frame[1]): + self.cmdui_obj.update_pack(force_draw=True) + return new_wx = math.floor((self.pack_frame[2] / 2) - (self.width / 2)) + self.pack_frame[0] if self.width <= self.pack_frame[2] else self.pack_frame[0] new_wy = math.floor((self.pack_frame[3] / 2) - (self.height / 2)) + self.pack_frame[1] if self.height <= self.pack_frame[3] else self.pack_frame[1] @@ -419,16 +413,6 @@ def generate_display(self): return (top, mid, btm) - @property - def width(self): - return len(self.display[0]) - - - @property - def height(self): - return len(self.display) - - class Button(Widget): diff --git a/CMDUI/console/consolemanager.py b/CMDUI/console/consolemanager.py index 65dba36..8a8104e 100644 --- a/CMDUI/console/consolemanager.py +++ b/CMDUI/console/consolemanager.py @@ -1,12 +1,13 @@ -from CMDUI.utils.stoppablethread import StoppableThread -from CMDUI.console.resizelistener import ResizeListener -import win32console -import pywintypes -import win32file import threading -import win32con import time +import pywintypes +import win32con +import win32console +import win32file +from CMDUI.console.resizelistener import ResizeListener +from CMDUI.utils.stoppablethread import StoppableThread + class ConsoleManager(StoppableThread): @@ -284,4 +285,4 @@ def free_console(self): win32console.AllocConsole() except: free_console=False - return free_console \ No newline at end of file + return free_console diff --git a/examples/cmdui_example.py b/examples/cmdui_example.py index 84fb717..cd278bb 100644 --- a/examples/cmdui_example.py +++ b/examples/cmdui_example.py @@ -1,10 +1,12 @@ import sys + sys.path.insert(0,'..') -import CMDUI as CMD import threading import time +import CMDUI as CMD + def counter(): btn_txt.set("Stop") @@ -39,14 +41,14 @@ def stopwatch(): # lab = CMD.Label(root, textvariable=txt) # lab.pack() -# frm = CMD.Frame(root) -# frm.pack() +frm = CMD.Frame(root) +frm.pack() # but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) # but.pack() -lab = CMD.Label(root, textvariable=txt) +lab = CMD.Label(frm, textvariable=txt) lab.pack(side="bottom") but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) @@ -62,4 +64,4 @@ def stopwatch(): but.pack(side="left") -root.mainloop() \ No newline at end of file +root.mainloop() diff --git a/examples/frames_example.py b/examples/frames_example.py new file mode 100644 index 0000000..28835d4 --- /dev/null +++ b/examples/frames_example.py @@ -0,0 +1,63 @@ +import sys + +sys.path.insert(0,'..') + +import threading +import time + +import CMDUI as CMD + + +def counter(): + btn_txt.set("Stop") + + tt = time.time() + while running: + t = f"{(time.time()-tt)**10:.2f}" + txt.set(t) + time.sleep(0.01) + + btn_txt.set("Reset") + + +def stopwatch(): + if btn_txt.get() == "Reset": + btn_txt.set("Start") + txt.set("") + return + + global running + running = not running + threading.Thread(target=counter).start() + + +running = False +root = CMD.CMDUI() + +txt = CMD.StringVar() +btn_txt = CMD.StringVar() +btn_txt.set("Start") + +frm = CMD.Frame(root) +frm.pack(side="left") + + +lab = CMD.Label(frm, textvariable=txt) +lab.pack() + +but = CMD.Button(frm, textvariable=btn_txt, command=stopwatch) +but.pack() + +but = CMD.Button(frm, textvariable=btn_txt, command=stopwatch) +but.pack() + +frm2 = CMD.Frame(root) +frm2.pack(side="right", expand=True) + +but = CMD.Button(frm2, textvariable=btn_txt, command=stopwatch) +but.pack() + +but = CMD.Button(frm2, textvariable=btn_txt, command=stopwatch) +but.pack() + +root.mainloop() From 96189a84d43d0be60e93d051e4bfd8bab6b74059 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Sun, 8 Nov 2020 16:39:27 +0000 Subject: [PATCH 13/16] Removing redundant placement management file --- CMDUI/placementmanagement.py | 134 ----------------------------------- 1 file changed, 134 deletions(-) delete mode 100644 CMDUI/placementmanagement.py diff --git a/CMDUI/placementmanagement.py b/CMDUI/placementmanagement.py deleted file mode 100644 index 9ac2719..0000000 --- a/CMDUI/placementmanagement.py +++ /dev/null @@ -1,134 +0,0 @@ -class Pack: - """Geometry manager Pack.""" - - - def pack_configure(self): - """Pack a widget in the parent widget. Use as options: - after=widget - pack it after you have packed widget - anchor=NSEW (or subset) - position widget according to - given direction - before=widget - pack it before you will pack widget - expand=bool - expand widget if parent size grows - fill=NONE or X or Y or BOTH - fill widget if widget grows - in=master - use master to contain this widget - in_=master - see 'in' option description - ipadx=amount - add internal padding in x direction - ipady=amount - add internal padding in y direction - padx=amount - add padding in x direction - pady=amount - add padding in y direction - side=TOP or BOTTOM or LEFT or RIGHT - where to add this widget. - """ - pass - - pack = configure = config = pack_configure - - - def pack_forget(self): - """Unmap this widget and do not use it for the packing order.""" - pass - - forget = pack_forget - - - def pack_info(self): - """Return information about the packing options - for this widget.""" - pass - - info = pack_info - - -class Place: - """Geometry manager Place.""" - - - def place_configure(self): - """Place a widget in the parent widget. Use as options: - in=master - master relative to which the widget is placed - in_=master - see 'in' option description - x=amount - locate anchor of this widget at position x of master - y=amount - locate anchor of this widget at position y of master - relx=amount - locate anchor of this widget between 0.0 and 1.0 - relative to width of master (1.0 is right edge) - rely=amount - locate anchor of this widget between 0.0 and 1.0 - relative to height of master (1.0 is bottom edge) - anchor=NSEW (or subset) - position anchor according to given direction - width=amount - width of this widget in pixel - height=amount - height of this widget in pixel - relwidth=amount - width of this widget between 0.0 and 1.0 - relative to width of master (1.0 is the same width - as the master) - relheight=amount - height of this widget between 0.0 and 1.0 - relative to height of master (1.0 is the same - height as the master) - bordermode="inside" or "outside" - whether to take border width of - master widget into account - """ - pass - - place = configure = config = place_configure - - - def place_forget(self): - """Unmap this widget.""" - pass - - forget = place_forget - - - def place_info(self): - """Return information about the placing options - for this widget.""" - pass - - info = place_info - - -class Grid: - """Geometry manager Grid.""" - - - def grid_configure(self): - """Position a widget in the parent widget in a grid. Use as options: - column=number - use cell identified with given column (starting with 0) - columnspan=number - this widget will span several columns - in=master - use master to contain this widget - in_=master - see 'in' option description - ipadx=amount - add internal padding in x direction - ipady=amount - add internal padding in y direction - padx=amount - add padding in x direction - pady=amount - add padding in y direction - row=number - use cell identified with given row (starting with 0) - rowspan=number - this widget will span several rows - sticky=NSEW - if cell is larger on which sides will this - widget stick to the cell boundary - """ - pass - - grid = configure = config = grid_configure - - - def grid_forget(self): - """Unmap this widget.""" - pass - - forget = grid_forget - - - def grid_remove(self): - """Unmap this widget but remember the grid options.""" - pass - - remove = grid_remove - - - def grid_info(self): - """Return information about the options - for positioning this widget in a grid.""" - pass - - info = grid_info - - -class Widget(Pack, Place, Grid): - pass \ No newline at end of file From bc6150d7d9ffa59eb5c30106c6d4eaee5d72c781 Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Sun, 8 Nov 2020 16:40:40 +0000 Subject: [PATCH 14/16] Project restructure --- CMDUI/CMDUI.py | 624 +------------------------------------ CMDUI/__init__.py | 5 +- CMDUI/components.py | 291 +++++++++++++++++ CMDUI/widgets.py | 306 ++++++++++++++++++ examples/cmdui_example.py | 12 - examples/frames_example.py | 29 +- 6 files changed, 617 insertions(+), 650 deletions(-) create mode 100644 CMDUI/components.py create mode 100644 CMDUI/widgets.py diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index af0a240..391abb9 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -1,221 +1,5 @@ -import math -import random -import time - -from CMDUI.colors import get_color from CMDUI.console.consolemanager import ConsoleManager -from CMDUI.variables import BooleanVar, DoubleVar, IntVar, StringVar - - -class Frame: - - - def __init__(self, parent, x=0, y=0, width=0, height=0): - if isinstance(parent, CMDUI): - self.cmdui_obj = parent - elif isinstance(parent, Frame): - self.cmdui_obj = parent.cmdui_obj - - self.parent = parent - self.packed_items = [] - - self.x = x - self.y = y - self.width = width - self.height = height - - self.expand = False - self.side = "top" - self.fill = "none" - - - def pack(self, expand=False, side="top", fill="none"): - assert isinstance(expand, bool), \ - "Parameter 'expand' must be a boolean!" - assert side in ("top", "bottom", "left", "right"), \ - "Parameter 'side' must be one of the following 'top', 'bottom', 'left', or 'right'." - assert fill in ("none", "both", "x", "y"), \ - "Parameter 'fill' must be one of the following 'none', 'both', 'x', or 'y'." - - if self in self.parent.packed_items: - return False - self.parent.packed_items.append(self) - self.cmdui_obj.visable_widgets.append(self) - - self.expand = expand - self.side = side - self.fill = fill - - - def draw(self): - x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] - h = int(f"0x{random.choice(x)}f", 16) - self.paint_background(h) - - - def undraw(self): - pass - - - def paint_background(self, color): - self.cmdui_obj.console_manager.color_area( - self.x, - self.y, - self.width, - self.height, - color - ) - - - def update_pack(self, force_draw=False): - # https://www.tcl.tk/man/tcl8.6/TkCmd/pack.htm - - # PASS #1 - - width = max_width = 0 - height = max_height = 0 - - for widget in self.packed_items: - if widget.side == "top" or widget.side == "bottom": - tmp = widget.width + width - if tmp > max_width: - max_width = tmp - height += widget.height - else: - tmp = widget.height + height - if tmp > max_height: - max_height = tmp - width += widget.width - - if width > max_width: - max_width = width - if height > max_height: - max_height = height - - # Expand window or frame if required... - if max_width > self.width: - self.width = max_width - if max_height > self.height: - self.height = max_height - - # If window size already changed then just stop and try again in a mo... - # if max_width != self.width or max_height != self.height: - # self.update_pack() - - # PASS #2 - - cavity_x = self.x - cavity_y = self.y - - cavity_width = self.width - cavity_height = self.height - - for widget_num, widget in enumerate(self.packed_items): - if widget.side == "top" or widget.side == "bottom": - frame_width = cavity_width - frame_height = widget.height - if widget.expand: - frame_height += self.y_expansion(widget_num, cavity_height) - - cavity_height -= frame_height - if cavity_height < 0: - frame_height += cavity_height - cavity_height = 0 - - frame_x = cavity_x - if widget.side == "top": - frame_y = cavity_y - cavity_y += frame_height - else: - frame_y = cavity_y + cavity_height - else: - frame_height = cavity_height - frame_width = widget.width - if widget.expand: - frame_width += self.x_expansion(widget_num, cavity_width) - - cavity_width -= frame_width - if cavity_width < 0: - frame_width += cavity_width - cavity_width = 0 - - frame_y = cavity_y - if widget.side == "left": - frame_x = cavity_x - cavity_x += frame_width - else: - frame_x = cavity_x + cavity_width - - widget.pack_frame = [frame_x, frame_y, frame_width, frame_height] - - # Extra for CMDUI... - x = ["a","c","d","1","2","3","4","5","6","7","8","9"] - h = int(f"0x{random.choice(x)}f", 16) - self.cmdui_obj.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) - #time.sleep(0.7) - - new_wx = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x - new_wy = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y - - if not force_draw and new_wx == widget.x and new_wy == widget.y: - return - - widget.x = new_wx - widget.y = new_wy - - widget.draw() - if isinstance(widget, Frame): - widget.update_pack(force_draw=True) - - - def x_expansion(self, widget_num, cavity_width): - minExpand = cavity_width - num_expand = 0 - for widget_n in range(widget_num, len(self.packed_items)): - widget = self.packed_items[widget_n] - child_width = widget.width - - if widget.side == "top" or widget.side == "bottom": - if num_expand: - cur_expand = (cavity_width - child_width) / num_expand - if cur_expand < minExpand: - minExpand = cur_expand - else: - cavity_width -= child_width - if widget.expand: - num_expand += 1 - - if num_expand: - cur_expand = cavity_width / num_expand - if cur_expand < minExpand: - minExpand = cur_expand - - return int(minExpand) if not (minExpand < 0) else 0 - - - def y_expansion(self, widget_num, cavity_height): - minExpand = cavity_height - num_expand = 0 - for widget_n in range(widget_num, len(self.packed_items)): - widget = self.packed_items[widget_n] - child_height = widget.height - - if widget.side == "left" or widget.side == "right": - if num_expand: - cur_expand = (cavity_height - child_height) / num_expand - if cur_expand < minExpand: - minExpand = cur_expand - else: - cavity_height -= child_height - if widget.expand: - num_expand += 1 - - if num_expand: - cur_expand = cavity_height / num_expand - if cur_expand < minExpand: - minExpand = cur_expand - - return int(minExpand) if not (minExpand < 0) else 0 +from CMDUI.components import Frame, Widget class CMDUI(Frame): @@ -228,6 +12,7 @@ def __init__(self): on_resize=self.on_window_resize ) + self.cmdui_obj = self self.console_manager.init_console_output() super().__init__( @@ -278,408 +63,3 @@ def on_window_resize(self): self.width = self.console_manager.console_size[0] self.height = self.console_manager.console_size[1] self.update_pack(force_draw=True) - - -class Widget: - - - def __init__(self, parent, x=0, y=0): - if isinstance(parent, CMDUI): - self.cmdui_obj = parent - elif isinstance(parent, Frame): - self.cmdui_obj = parent.cmdui_obj - - self.parent = parent - self.x = x - self.y = y - - self.display = "" - - self.expand = False - self.side = "top" - self.fill = "none" - self.pack_frame = [0, 0, 0, 0] - - - def pack(self, expand=False, side="top", fill="none"): - assert isinstance(expand, bool), \ - "Parameter 'expand' must be a boolean!" - assert side in ("top", "bottom", "left", "right"), \ - "Parameter 'side' must be one of the following 'top', 'bottom', 'left', or 'right'." - assert fill in ("none", "both", "x", "y"), \ - "Parameter 'fill' must be one of the following 'none', 'both', 'x', or 'y'." - - if self in self.parent.packed_items: - return False - self.parent.packed_items.append(self) - self.cmdui_obj.visable_widgets.append(self) - - self.expand = expand - self.side = side - self.fill = fill - - - def re_pack(self): - - # Need to check if the windows new position is too big for the current frame! - if self.width > (self.pack_frame[2]-self.pack_frame[0]) or self.height > (self.pack_frame[3]-self.pack_frame[1]): - self.cmdui_obj.update_pack(force_draw=True) - return - - new_wx = math.floor((self.pack_frame[2] / 2) - (self.width / 2)) + self.pack_frame[0] if self.width <= self.pack_frame[2] else self.pack_frame[0] - new_wy = math.floor((self.pack_frame[3] / 2) - (self.height / 2)) + self.pack_frame[1] if self.height <= self.pack_frame[3] else self.pack_frame[1] - self.undraw() - self.x = new_wx - self.y = new_wy - self.draw() - - - def draw(self): - x_coord = self.x if self.x > 0 else 0 - y_coord = self.y if self.y > 0 else 0 - - for i in range(len(self.display)): - self.cmdui_obj.console_manager.print_pos(x_coord, y_coord+i, self.display[i]) - - - def undraw(self): - x_coord = self.x if self.x > 0 else 0 - y_coord = self.y if self.y > 0 else 0 - - for i in range(len(self.display)): - self.cmdui_obj.console_manager.print_pos(x_coord, y_coord+i, " "*len(self.display[i])) - - - def check_inside(self, x, y): - if x >= self.x and x < self.x+self.width and \ - y >= self.y and y < self.y+self.height: - return True - else: - return False - - - def check_hover(self, x, y): - pass - - - def on_press(self, x, y): - pass - - - def on_release(self): - pass - - - @property - def width(self): - return len(self.display[0]) - - - @property - def height(self): - return len(self.display) - - -class Label(Widget): - - - def __init__(self, cmdui_obj, text="", textvariable=None, x=0, y=0): - super().__init__(cmdui_obj, x, y) - - if textvariable: - self.text = textvariable.get() - textvariable.widgets.append(self) - else: - self.text = text - - self.display = self.generate_display() - - - def update_text(self, text): - pk_update_needed = True if len(self.text) != len(text) else False - - self.text = text - self.display = self.generate_display() - - if pk_update_needed: - self.re_pack() - - - def generate_display(self): - top = f'┌─{"─"*len(self.text)}─┐' - mid = f'│ { self.text } │' - btm = f'└─{"─"*len(self.text)}─┘' - - return (top, mid, btm) - - -class Button(Widget): - - - def __init__(self, cmdui_obj, text="", textvariable=None, x=0, y=0, command=None): - super().__init__(cmdui_obj, x, y) - - if textvariable: - self.text = textvariable.get() - textvariable.widgets.append(self) - else: - self.text = text - - if command: - self.command = command - - self.display = self.generate_display() - self.hovered = False - self.pressed = False - - - def command(self): - pass - - - def update_text(self, text): - pk_update_needed = True if len(self.text) != len(text) else False - - self.text = text - if self.hovered: - self.display = self.generate_active_display() - else: - self.display = self.generate_display() - - if pk_update_needed: - self.re_pack() - - - def generate_display(self): - top = f'┌─{"─"*len(self.text)}─┐' - mid = f'│ { self.text } │' - btm = f'└─{"─"*len(self.text)}─┘' - return (top, mid, btm) - - - def generate_active_display(self): - top = f'╔═{"═"*len(self.text)}═╗' - mid = f'║ { self.text } ║' - btm = f'╚═{"═"*len(self.text)}═╝' - return (top, mid, btm) - - - def draw_hover(self): - self.display = self.generate_active_display() - self.draw() - - - def draw_pressed(self): - FOREGROUND_INTENSITY = 0x0008 - - cur_color = self.cmdui_obj.console_manager.get_console_color_code() - - # Bitwise XOR using FOREGROUND_INTENSITY; Keeps the colour, inverts the intensity. - select_color = cur_color ^ FOREGROUND_INTENSITY - - self.cmdui_obj.console_manager.color_area( - self.x, - self.y, - self.width, - self.height, - select_color - ) - - - def check_hover(self, x, y): - if self.check_inside(x, y) and self.hovered == False: - self.hovered = True - self.draw_hover() - elif not self.check_inside(x, y) and self.hovered == True: - if not self.pressed: - self.hovered = False - self.display = self.generate_display() - self.draw() - - - def on_press(self, x, y): - self.draw_pressed() - self.pressed = True - - - def on_release(self): - if self.pressed: - self.command() - self.pressed = False - - -class Input(Widget): - - - def __init__(self, cmdui_obj, text, x=0, y=0): - super().__init__(cmdui_obj, x=x, y=y) - - self.text = text - - self.display = self.generate_input(text) - self.display_active = self.generate_hovered_input(text) - - self.width = len(self.display[0]) - self.height = len(self.display) - - - def generate_input(self, text): - top = f'┌─{"─"*len(text)}─┐' - mid = f'│ { text } │' - btm = f'└─{"─"*len(text)}─┘' - - return (top, mid, btm) - - - def generate_hovered_input(self, text): - top = f'╔═{"═"*len(text)}═╗' - mid = f'║ { text } ║' - btm = f'╚═{"═"*len(text)}═╝' - - return (top, mid, btm) - - - def generate_active_input(self, text): - top = f'╔═{"═"*len(text)}═╗' - mid = f'║ {" "*len(text)} ║' - btm = f'╚═{"═"*len(text)}═╝' - - return (top, mid, btm) - - - def draw_hover(self): - self.display = self.generate_hovered_input(self.text) - self.draw() - - - def draw_pressed(self): - cur_color = self.cmdui_obj.console_manager.get_console_color_code() - - # Bitwise XOR using FOREGROUND_INTENSITY; Keeps the colour, inverts the intensity. - FOREGROUND_INTENSITY = 0x0008 - self.cmdui_obj.console_manager.set_color(cur_color ^ FOREGROUND_INTENSITY) - - self.draw() - - # Handle user input; currently needs rewrite. - self.handle_input() - - self.cmdui_obj.console_manager.set_color(cur_color) - - - def handle_input(self): - # We can use the new console manager input buffer to fix the freezing with this! - - self.cmdui_obj.console_manager.print_pos(self.x+1, self.y+1, "") - - #cur.move(self.x+1, self.y+1) - user_input = input() - - self.cmdui_obj.console_manager.print_pos(self.x+1, self.y+1, ' '*len(user_input)) - # self.cmdui_obj.console_manager.print_pos(0, 0, f'User said: {user_input}') - - - def update_widget(self, x, y): - if self.check_inside(x, y, self) and self.hovered == False: - self.hovered = True - self.draw_hover() - elif not self.check_inside(x, y, self) and self.hovered == True: - self.hovered = False - self.display = self.generate_input(self.text) - self.draw() - - - def is_clicked(self, x, y): - if self.check_inside(x, y, self): - self.draw_pressed() - #self.draw_hover() - return True - else: - return False - - -class DrawPad(Widget): - - - def __init__(self, cmdui_obj, x=0, y=0, h=1, w=1): - super().__init__(cmdui_obj, x=x, y=y) - - self.width = w - self.height = h - - self.display = self.generate_pad() - - - def generate_pad(self): - pad = ["┌──" + "─"*(self.width-6) + "──┐\n"] - for _ in range(self.height-2): - pad.append("│ " + " "*(self.width-6) + " │\n") - pad.append("└──" + "─"*(self.width-6) + "──┘") - return pad - - - def draw(self): - for i in range(len(self.display)): - self.cmdui_obj.console_manager.print_pos(self.x, self.y+i, self.display[i]) - - - def undraw(self): - for i in range(len(self.display)): - self.cmdui_obj.console_manager.print_pos(self.x+len(self.display[i]), self.y+i, " "*len(self.display[i])) - - - def draw_pressed(self): - pass - - - def update_widget(self, x, y): - if self.check_inside(x, y, self): - self.cmdui_obj.console_manager.print_pos(x, y, "X") - - -class Menu(Widget): - - - def __init__(self, cmdui_obj): - super().__init__(cmdui_obj, x=0, y=0) - - self.width = self.cmdui_obj.main_frame.width - self.height = 2 - - self.options = [] - - self.display = self.generate_menu() - - - def generate_menu(self): - btns = ''.join(f' {option.text} │' for option in self.options) - btm = ''.join(f'─{"─"*len(option.text)}─┴' for option in self.options) - - if len(btns) < self.width: - btm = f'{btm}{"─"*(self.width - len(btm))}' - - return (btns, btm) - - - def draw(self): - self.x = 0 - self.y = 0 - self.width = self.cmdui_obj.main_frame.width - self.display = self.generate_menu() - - for i in range(len(self.display)): - self.cmdui_obj.console_manager.print_pos(self.x, self.y+i, self.display[i]) - - - def undraw(self): - for i in range(len(self.display)): - self.cmdui_obj.console_manager.print_pos(0, self.y+i, " "*len(self.display[i])) - - -class MenuOption: - - - def __init__(self, menu_obj, text): - menu_obj.options.append(self) - - self.text = text diff --git a/CMDUI/__init__.py b/CMDUI/__init__.py index 8cd3d6d..5ba449b 100644 --- a/CMDUI/__init__.py +++ b/CMDUI/__init__.py @@ -1 +1,4 @@ -from .CMDUI import * \ No newline at end of file +from .CMDUI import * +from .widgets import * +from .variables import * +from .components import Frame \ No newline at end of file diff --git a/CMDUI/components.py b/CMDUI/components.py new file mode 100644 index 0000000..8e44bc5 --- /dev/null +++ b/CMDUI/components.py @@ -0,0 +1,291 @@ +import math +import random + + +class Pack: + """Geometry manager Pack.""" + + + def pack(self, expand=False, side="top", fill="none"): + assert isinstance(expand, bool), \ + "Parameter 'expand' must be a boolean!" + assert side in ("top", "bottom", "left", "right"), \ + "Parameter 'side' must be one of the following 'top', 'bottom', 'left', or 'right'." + assert fill in ("none", "both", "x", "y"), \ + "Parameter 'fill' must be one of the following 'none', 'both', 'x', or 'y'." + + if self in self.parent.packed_items: + return False + self.parent.packed_items.append(self) + self.cmdui_obj.visable_widgets.append(self) + + self.expand = expand + self.side = side + self.fill = fill + + +class Frame(Pack): + + + def __init__(self, parent, x=0, y=0, width=0, height=0): + self.parent = parent + self.cmdui_obj = self.parent.cmdui_obj + + self.x = x + self.y = y + self.width = width + self.height = height + + self.packed_items = [] + self.expand = False + self.side = "top" + self.fill = "none" + + + def draw(self): + x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] + color_hex = int(f"0x{random.choice(x)}f", 16) + self.paint_background(color_hex) + + + def undraw(self): + pass + + + def paint_background(self, color): + self.cmdui_obj.console_manager.color_area( + self.x, + self.y, + self.width, + self.height, + color + ) + + + def update_pack(self, force_draw=False): + # https://www.tcl.tk/man/tcl8.6/TkCmd/pack.htm + + # PASS #1 + + width = max_width = 0 + height = max_height = 0 + + for widget in self.packed_items: + if widget.side == "top" or widget.side == "bottom": + tmp = widget.width + width + if tmp > max_width: + max_width = tmp + height += widget.height + else: + tmp = widget.height + height + if tmp > max_height: + max_height = tmp + width += widget.width + + if width > max_width: + max_width = width + if height > max_height: + max_height = height + + # Expand window or frame if required... + if max_width > self.width: + self.width = max_width + if max_height > self.height: + self.height = max_height + + # If window size already changed then just stop and try again in a mo... + # if max_width != self.width or max_height != self.height: + # self.update_pack() + + # PASS #2 + + cavity_x = self.x + cavity_y = self.y + + cavity_width = self.width + cavity_height = self.height + + for widget_num, widget in enumerate(self.packed_items): + if widget.side == "top" or widget.side == "bottom": + frame_width = cavity_width + frame_height = widget.height + if widget.expand: + frame_height += self.y_expansion(widget_num, cavity_height) + + cavity_height -= frame_height + if cavity_height < 0: + frame_height += cavity_height + cavity_height = 0 + + frame_x = cavity_x + if widget.side == "top": + frame_y = cavity_y + cavity_y += frame_height + else: + frame_y = cavity_y + cavity_height + else: + frame_height = cavity_height + frame_width = widget.width + if widget.expand: + frame_width += self.x_expansion(widget_num, cavity_width) + + cavity_width -= frame_width + if cavity_width < 0: + frame_width += cavity_width + cavity_width = 0 + + frame_y = cavity_y + if widget.side == "left": + frame_x = cavity_x + cavity_x += frame_width + else: + frame_x = cavity_x + cavity_width + + widget.pack_frame = [frame_x, frame_y, frame_width, frame_height] + + # Extra for CMDUI... + x = ["a","c","d","1","2","3","4","5","6","7","8","9"] + h = int(f"0x{random.choice(x)}f", 16) + self.cmdui_obj.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) + #time.sleep(0.7) + + new_wx = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x + new_wy = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y + + if not force_draw and new_wx == widget.x and new_wy == widget.y: + return + + widget.x = new_wx + widget.y = new_wy + + widget.draw() + if isinstance(widget, Frame): + widget.update_pack(force_draw=True) + + + def x_expansion(self, widget_num, cavity_width): + minExpand = cavity_width + num_expand = 0 + for widget_n in range(widget_num, len(self.packed_items)): + widget = self.packed_items[widget_n] + child_width = widget.width + + if widget.side == "top" or widget.side == "bottom": + if num_expand: + cur_expand = (cavity_width - child_width) / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + else: + cavity_width -= child_width + if widget.expand: + num_expand += 1 + + if num_expand: + cur_expand = cavity_width / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + + return int(minExpand) if not (minExpand < 0) else 0 + + + def y_expansion(self, widget_num, cavity_height): + minExpand = cavity_height + num_expand = 0 + for widget_n in range(widget_num, len(self.packed_items)): + widget = self.packed_items[widget_n] + child_height = widget.height + + if widget.side == "left" or widget.side == "right": + if num_expand: + cur_expand = (cavity_height - child_height) / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + else: + cavity_height -= child_height + if widget.expand: + num_expand += 1 + + if num_expand: + cur_expand = cavity_height / num_expand + if cur_expand < minExpand: + minExpand = cur_expand + + return int(minExpand) if not (minExpand < 0) else 0 + + +class Widget(Pack): + + + def __init__(self, parent, x=0, y=0): + self.parent = parent + self.cmdui_obj = self.parent.cmdui_obj + + self.x = x + self.y = y + self.display = "" + + self.pack_frame = [0, 0, 0, 0] + self.expand = False + self.side = "top" + self.fill = "none" + + + def re_pack(self): + + # Need to check if the windows new position is too big for the current frame! + if self.width > (self.pack_frame[2]-self.pack_frame[0]) or self.height > (self.pack_frame[3]-self.pack_frame[1]): + self.cmdui_obj.update_pack(force_draw=True) + return + + new_wx = math.floor((self.pack_frame[2] / 2) - (self.width / 2)) + self.pack_frame[0] if self.width <= self.pack_frame[2] else self.pack_frame[0] + new_wy = math.floor((self.pack_frame[3] / 2) - (self.height / 2)) + self.pack_frame[1] if self.height <= self.pack_frame[3] else self.pack_frame[1] + self.undraw() + self.x = new_wx + self.y = new_wy + self.draw() + + + def draw(self): + x_coord = self.x if self.x > 0 else 0 + y_coord = self.y if self.y > 0 else 0 + + for i, segment in enumerate(self.display): + self.cmdui_obj.console_manager.print_pos(x_coord, y_coord+i, segment) + + + def undraw(self): + x_coord = self.x if self.x > 0 else 0 + y_coord = self.y if self.y > 0 else 0 + + for i, segment in enumerate(self.display): + self.cmdui_obj.console_manager.print_pos(x_coord, y_coord+i, " "*len(segment)) + + + def check_inside(self, x, y): + if x >= self.x and x < self.x+self.width and \ + y >= self.y and y < self.y+self.height: + return True + else: + return False + + + def check_hover(self, x, y): + pass + + + def on_press(self, x, y): + pass + + + def on_release(self): + pass + + + @property + def width(self): + return len(self.display[0]) + + + @property + def height(self): + return len(self.display) diff --git a/CMDUI/widgets.py b/CMDUI/widgets.py new file mode 100644 index 0000000..b6246a5 --- /dev/null +++ b/CMDUI/widgets.py @@ -0,0 +1,306 @@ +from .components import Widget + + +class Label(Widget): + + + def __init__(self, cmdui_obj, text="", textvariable=None, x=0, y=0): + super().__init__(cmdui_obj, x, y) + + if textvariable: + self.text = textvariable.get() + textvariable.widgets.append(self) + else: + self.text = text + + self.display = self.generate_display() + + + def update_text(self, text): + pk_update_needed = True if len(self.text) != len(text) else False + + self.text = text + self.display = self.generate_display() + + if pk_update_needed: + self.re_pack() + + + def generate_display(self): + top = f'┌─{"─"*len(self.text)}─┐' + mid = f'│ { self.text } │' + btm = f'└─{"─"*len(self.text)}─┘' + + return (top, mid, btm) + + +class Button(Widget): + + + def __init__(self, cmdui_obj, text="", textvariable=None, x=0, y=0, command=None): + super().__init__(cmdui_obj, x, y) + + if textvariable: + self.text = textvariable.get() + textvariable.widgets.append(self) + else: + self.text = text + + if command: + self.command = command + + self.display = self.generate_display() + self.hovered = False + self.pressed = False + + + def command(self): + pass + + + def update_text(self, text): + pk_update_needed = True if len(self.text) != len(text) else False + + self.text = text + if self.hovered: + self.display = self.generate_active_display() + else: + self.display = self.generate_display() + + if pk_update_needed: + self.re_pack() + + + def generate_display(self): + top = f'┌─{"─"*len(self.text)}─┐' + mid = f'│ { self.text } │' + btm = f'└─{"─"*len(self.text)}─┘' + return (top, mid, btm) + + + def generate_active_display(self): + top = f'╔═{"═"*len(self.text)}═╗' + mid = f'║ { self.text } ║' + btm = f'╚═{"═"*len(self.text)}═╝' + return (top, mid, btm) + + + def draw_hover(self): + self.display = self.generate_active_display() + self.draw() + + + def draw_pressed(self): + FOREGROUND_INTENSITY = 0x0008 + + cur_color = self.cmdui_obj.console_manager.get_console_color_code() + + # Bitwise XOR using FOREGROUND_INTENSITY; Keeps the colour, inverts the intensity. + select_color = cur_color ^ FOREGROUND_INTENSITY + + self.cmdui_obj.console_manager.color_area( + self.x, + self.y, + self.width, + self.height, + select_color + ) + + + def check_hover(self, x, y): + if self.check_inside(x, y) and self.hovered == False: + self.hovered = True + self.draw_hover() + elif not self.check_inside(x, y) and self.hovered == True: + if not self.pressed: + self.hovered = False + self.display = self.generate_display() + self.draw() + + + def on_press(self, x, y): + self.draw_pressed() + self.pressed = True + + + def on_release(self): + if self.pressed: + self.command() + self.pressed = False + + +class Input(Widget): + + + def __init__(self, cmdui_obj, text, x=0, y=0): + super().__init__(cmdui_obj, x=x, y=y) + + self.text = text + + self.display = self.generate_input(text) + self.display_active = self.generate_hovered_input(text) + + self.width = len(self.display[0]) + self.height = len(self.display) + + + def generate_input(self, text): + top = f'┌─{"─"*len(text)}─┐' + mid = f'│ { text } │' + btm = f'└─{"─"*len(text)}─┘' + + return (top, mid, btm) + + + def generate_hovered_input(self, text): + top = f'╔═{"═"*len(text)}═╗' + mid = f'║ { text } ║' + btm = f'╚═{"═"*len(text)}═╝' + + return (top, mid, btm) + + + def generate_active_input(self, text): + top = f'╔═{"═"*len(text)}═╗' + mid = f'║ {" "*len(text)} ║' + btm = f'╚═{"═"*len(text)}═╝' + + return (top, mid, btm) + + + def draw_hover(self): + self.display = self.generate_hovered_input(self.text) + self.draw() + + + def draw_pressed(self): + cur_color = self.cmdui_obj.console_manager.get_console_color_code() + + # Bitwise XOR using FOREGROUND_INTENSITY; Keeps the colour, inverts the intensity. + FOREGROUND_INTENSITY = 0x0008 + self.cmdui_obj.console_manager.set_color(cur_color ^ FOREGROUND_INTENSITY) + + self.draw() + + # Handle user input; currently needs rewrite. + self.handle_input() + + self.cmdui_obj.console_manager.set_color(cur_color) + + + def handle_input(self): + # We can use the new console manager input buffer to fix the freezing with this! + + self.cmdui_obj.console_manager.print_pos(self.x+1, self.y+1, "") + + #cur.move(self.x+1, self.y+1) + user_input = input() + + self.cmdui_obj.console_manager.print_pos(self.x+1, self.y+1, ' '*len(user_input)) + # self.cmdui_obj.console_manager.print_pos(0, 0, f'User said: {user_input}') + + + def update_widget(self, x, y): + if self.check_inside(x, y, self) and self.hovered == False: + self.hovered = True + self.draw_hover() + elif not self.check_inside(x, y, self) and self.hovered == True: + self.hovered = False + self.display = self.generate_input(self.text) + self.draw() + + + def is_clicked(self, x, y): + if self.check_inside(x, y, self): + self.draw_pressed() + #self.draw_hover() + return True + else: + return False + + +class DrawPad(Widget): + + + def __init__(self, cmdui_obj, x=0, y=0, h=1, w=1): + super().__init__(cmdui_obj, x=x, y=y) + + self.width = w + self.height = h + + self.display = self.generate_pad() + + + def generate_pad(self): + pad = ["┌──" + "─"*(self.width-6) + "──┐\n"] + for _ in range(self.height-2): + pad.append("│ " + " "*(self.width-6) + " │\n") + pad.append("└──" + "─"*(self.width-6) + "──┘") + return pad + + + def draw(self): + for i in range(len(self.display)): + self.cmdui_obj.console_manager.print_pos(self.x, self.y+i, self.display[i]) + + + def undraw(self): + for i in range(len(self.display)): + self.cmdui_obj.console_manager.print_pos(self.x+len(self.display[i]), self.y+i, " "*len(self.display[i])) + + + def draw_pressed(self): + pass + + + def update_widget(self, x, y): + if self.check_inside(x, y, self): + self.cmdui_obj.console_manager.print_pos(x, y, "X") + + +class Menu(Widget): + + + def __init__(self, cmdui_obj): + super().__init__(cmdui_obj, x=0, y=0) + + self.width = self.cmdui_obj.main_frame.width + self.height = 2 + + self.options = [] + + self.display = self.generate_menu() + + + def generate_menu(self): + btns = ''.join(f' {option.text} │' for option in self.options) + btm = ''.join(f'─{"─"*len(option.text)}─┴' for option in self.options) + + if len(btns) < self.width: + btm = f'{btm}{"─"*(self.width - len(btm))}' + + return (btns, btm) + + + def draw(self): + self.x = 0 + self.y = 0 + self.width = self.cmdui_obj.main_frame.width + self.display = self.generate_menu() + + for i in range(len(self.display)): + self.cmdui_obj.console_manager.print_pos(self.x, self.y+i, self.display[i]) + + + def undraw(self): + for i in range(len(self.display)): + self.cmdui_obj.console_manager.print_pos(0, self.y+i, " "*len(self.display[i])) + + +class MenuOption: + + + def __init__(self, menu_obj, text): + menu_obj.options.append(self) + + self.text = text \ No newline at end of file diff --git a/examples/cmdui_example.py b/examples/cmdui_example.py index cd278bb..2542365 100644 --- a/examples/cmdui_example.py +++ b/examples/cmdui_example.py @@ -38,30 +38,18 @@ def stopwatch(): btn_txt = CMD.StringVar() btn_txt.set("Start") -# lab = CMD.Label(root, textvariable=txt) -# lab.pack() - frm = CMD.Frame(root) frm.pack() - -# but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) -# but.pack() - - lab = CMD.Label(frm, textvariable=txt) lab.pack(side="bottom") but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) but.pack(side="top", expand=True) - but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) but.pack(side="right") - but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) but.pack() - but = CMD.Button(root, textvariable=btn_txt, command=stopwatch) but.pack(side="left") - root.mainloop() diff --git a/examples/frames_example.py b/examples/frames_example.py index 28835d4..11c4a0f 100644 --- a/examples/frames_example.py +++ b/examples/frames_example.py @@ -38,26 +38,25 @@ def stopwatch(): btn_txt = CMD.StringVar() btn_txt.set("Start") -frm = CMD.Frame(root) -frm.pack(side="left") +frame_one = CMD.Frame(root) +counter_label = CMD.Label(frame_one, textvariable=txt) +button_one = CMD.Button(frame_one, textvariable=btn_txt, command=stopwatch) +button_two = CMD.Button(frame_one, textvariable=btn_txt, command=stopwatch) -lab = CMD.Label(frm, textvariable=txt) -lab.pack() +frame_one.pack(side="left") +counter_label.pack() +button_one.pack() +button_two.pack() -but = CMD.Button(frm, textvariable=btn_txt, command=stopwatch) -but.pack() -but = CMD.Button(frm, textvariable=btn_txt, command=stopwatch) -but.pack() +frame_two = CMD.Frame(root) +frame2_button_one = CMD.Button(frame_two, textvariable=btn_txt, command=stopwatch) +frame2_button_two = CMD.Button(frame_two, textvariable=btn_txt, command=stopwatch) -frm2 = CMD.Frame(root) -frm2.pack(side="right", expand=True) +frame_two.pack(side="right", expand=True) +frame2_button_one.pack() +frame2_button_two.pack() -but = CMD.Button(frm2, textvariable=btn_txt, command=stopwatch) -but.pack() - -but = CMD.Button(frm2, textvariable=btn_txt, command=stopwatch) -but.pack() root.mainloop() From 68f4dc3caabf68607f392ede5d9ce687dc4cf8bd Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Thu, 12 Nov 2020 13:46:07 +0000 Subject: [PATCH 15/16] 0.2.0 prep --- CMDUI/CMDUI.py | 4 +++- CMDUI/colors.py | 6 +++++ CMDUI/components.py | 15 +++++------- CMDUI/console/consolemanager.py | 2 ++ examples/stopwatch.py | 41 +++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 examples/stopwatch.py diff --git a/CMDUI/CMDUI.py b/CMDUI/CMDUI.py index 391abb9..291adf9 100644 --- a/CMDUI/CMDUI.py +++ b/CMDUI/CMDUI.py @@ -30,7 +30,7 @@ def mainloop(self): self.console_manager.start() # (DISABLED FOR DEBUG) - # self.console_manager.set_cursor_visable(False) + self.console_manager.set_cursor_visable(False) # Initially building widgets! self.update_pack() @@ -62,4 +62,6 @@ def on_mouse_click(self, x, y, button_state): def on_window_resize(self): self.width = self.console_manager.console_size[0] self.height = self.console_manager.console_size[1] + + self.console_manager.set_cursor_visable(False) self.update_pack(force_draw=True) diff --git a/CMDUI/colors.py b/CMDUI/colors.py index be66591..8bdc288 100644 --- a/CMDUI/colors.py +++ b/CMDUI/colors.py @@ -1,3 +1,5 @@ +import random + _colors = { "black" : 0x0, "blue" : 0x1, @@ -43,6 +45,10 @@ def get_color(fg_color_name: str, bg_color_name: str = "") -> int: return fg +def get_random_background_color(): + return get_bg_code(random.choice(list(_colors.keys()))) + + def get_color_hex(fg_color_name: str, bg_color_name: str) -> hex: return hex(get_color(fg_color_name, bg_color_name)) diff --git a/CMDUI/components.py b/CMDUI/components.py index 8e44bc5..103f972 100644 --- a/CMDUI/components.py +++ b/CMDUI/components.py @@ -43,9 +43,7 @@ def __init__(self, parent, x=0, y=0, width=0, height=0): def draw(self): - x = ["a","c","d","0","1","2","3","4","5","6","7","8","9"] - color_hex = int(f"0x{random.choice(x)}f", 16) - self.paint_background(color_hex) + pass def undraw(self): @@ -90,8 +88,10 @@ def update_pack(self, force_draw=False): # Expand window or frame if required... if max_width > self.width: self.width = max_width + self.parent.update_pack() if max_height > self.height: self.height = max_height + self.parent.update_pack() # If window size already changed then just stop and try again in a mo... # if max_width != self.width or max_height != self.height: @@ -143,22 +143,19 @@ def update_pack(self, force_draw=False): widget.pack_frame = [frame_x, frame_y, frame_width, frame_height] - # Extra for CMDUI... - x = ["a","c","d","1","2","3","4","5","6","7","8","9"] - h = int(f"0x{random.choice(x)}f", 16) - self.cmdui_obj.console_manager.color_area(frame_x, frame_y, frame_width, frame_height, h) - #time.sleep(0.7) - new_wx = math.floor((frame_width / 2) - (widget.width / 2)) + frame_x if widget.width <= frame_width else frame_x new_wy = math.floor((frame_height / 2) - (widget.height / 2)) + frame_y if widget.height <= frame_height else frame_y if not force_draw and new_wx == widget.x and new_wy == widget.y: return + widget.undraw() + widget.x = new_wx widget.y = new_wy widget.draw() + if isinstance(widget, Frame): widget.update_pack(force_draw=True) diff --git a/CMDUI/console/consolemanager.py b/CMDUI/console/consolemanager.py index 8a8104e..c51e591 100644 --- a/CMDUI/console/consolemanager.py +++ b/CMDUI/console/consolemanager.py @@ -221,6 +221,8 @@ def print_pos(self, x, y, text): def _print_pos(self, x, y, text): + if x > self.console_size[0] or y > self.console_size[1]: + return -1 pos = win32console.PyCOORDType(x, y) with self._thread_lock: self.console_output.SetConsoleCursorPosition(pos) diff --git a/examples/stopwatch.py b/examples/stopwatch.py new file mode 100644 index 0000000..fa8430d --- /dev/null +++ b/examples/stopwatch.py @@ -0,0 +1,41 @@ +import CMDUI as CMD +import threading +import time + + +def counter(): + btn_txt.set("Stop") + + tt = time.time() + while running: + lab_txt.set(f"{time.time()-tt:.2f}") + time.sleep(0.01) + + btn_txt.set("Reset") + + +def stopwatch(): + if btn_txt.get() == "Reset": + btn_txt.set("Start") + lab_txt.set("") + return + + global running + running = not running + threading.Thread(target=counter).start() + + +cmdui = CMD.CMDUI() +running = False + +lab_txt = CMD.StringVar() +btn_txt = CMD.StringVar() +btn_txt.set("Start") + +lab = CMD.Label(cmdui, textvariable=lab_txt) +lab.pack() + +but = CMD.Button(cmdui, textvariable=btn_txt, command=stopwatch) +but.pack() + +cmdui.mainloop() \ No newline at end of file From 17b8e8a8aa8ad9a8d521eed906c3421062c853dd Mon Sep 17 00:00:00 2001 From: Dan Burrows Date: Thu, 12 Nov 2020 13:46:24 +0000 Subject: [PATCH 16/16] Update readme and version --- README.md | 10 ++++------ setup.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d9a69e9..7f6c48c 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,11 @@ This is a project to build an easy-to-use GUI toolkit using just the windows con Eventually I aim to mirror the placement methods available in Tkinter such as Pack, Place, and Grid. I would also like to make a version of CMDUI for ANSI code driven terminals in the same easy-to-use style, although in the meantime CMDUI is only runnable in the windows console (or CMD). +## Notes for 0.2.0 release +Thanks for checking out the project! CMDUI is still in very early development and this release has added a new pack algorithm. This algorithm has been a tricky one to implement and there are still a lot of bugs that need to be ironed out. Feel free to open an issue if you encounter any. I hope you enjoy. :-) + ## Installation -Clone the repo, download as a zip file, or use the following command... +CMDUI is now available on pip... ```sh pip install CMDUI ``` @@ -58,8 +61,3 @@ but.pack() cmdui.mainloop() ``` - -## Current Issues -Due to the way lines of text are wrapped in CMD the GUI had a tendency to explode with artifacts all over the place when the window is resized. The temporary fix I have implemented for this is very unstable and can cause crashes occasionally but I am currently still looking into a better fix for this problem. - -A means of toggling "Wrap text output on resize" really needs to be implemented in the C/C++ bindings for windows and subsequently ctypes or win32console for this problem to be fixed completely. \ No newline at end of file diff --git a/setup.py b/setup.py index 492fdef..727c7a6 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ EMAIL = 'dannyburrows@protonmail.com' AUTHOR = 'Dan Burrows' REQUIRES_PYTHON = '>=3.6.0' -VERSION = '0.1.0' +VERSION = '0.2.0' here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: