diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b44bed --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# User-created directories +themes/ +Fonts/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Testing +.pytest_cache/ +.coverage +htmlcov/ + +# Virtual Environment +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/ChooseFontsPlugin.py b/ChooseFontsPlugin.py index 3ae49e4..03e5b59 100644 --- a/ChooseFontsPlugin.py +++ b/ChooseFontsPlugin.py @@ -1,165 +1,265 @@ -import dearpygui.dearpygui as dpg -import os -#import ctypes -# Include the following code before showing the viewport/calling `dearpygui.dearpygui.show_viewport`. -#ctypes.windll.shcore.SetProcessDpiAwareness(2) -class ChooseFontsPlugin(): - - def __init__(self): - self.ignore = 'NO CUSTOM FONT, IGNORE' - self.font_registry = dpg.add_font_registry() - self.fontDict = dict() - self.create_folders() - self.create_font_library() - self.create_font_window() - self.create_font_menu() - - def create_folders(self): - if not os.path.exists('Fonts'): - os.mkdir('Fonts') - if not os.path.exists('Fonts/USERFONT'): - with open('Fonts/USERFONT', 'w') as f: - f.write(self.ignore) - if not os.path.exists('Fonts/USERSIZE'): - with open('Fonts/USERSIZE', 'w') as f: - f.write("16") - if not os.path.exists('Fonts/USERSCALE'): - with open('Fonts/USERSCALE', 'w') as f: - f.write("1") - - - def create_font_library(self): - with open('Fonts/USERFONT', 'r') as f: - self.userFont = f.read() - if not self.userFont: self.userFont = self.ignore - with open('Fonts/USERSIZE', 'r') as f: - try: self.userSize = int(f.read()) - except: self.userSize = 16 - if not self.userSize: self.userSize = 16 - with open('Fonts/USERSCALE', 'r') as f: - try: self.userScale = float(f.read()) - except: self.userScale = 1 - if not self.userScale: self.userScale = 1 - self.fontDict["YOUR FONTS"] = None - for filename in os.listdir('Fonts'): - if filename.endswith((".ttf","otf")): - self.fontDict[filename] = f"Fonts/{filename}" - with dpg.font(self.fontDict[filename], 16, parent=self.font_registry) as f: - dpg.add_font_range_hint(dpg.mvFontRangeHint_Default) - dpg.add_font_range_hint(dpg.mvFontRangeHint_Cyrillic) - dpg.add_font_range(0x0, 0xF) - dpg.add_font_range(0x00, 0xFF) - dpg.add_font_range(0x000, 0xFFF) - dpg.add_font_range(0x0000, 0xFFFF) - dpg.bind_font(f) - self.fontDict["WINDOWS FONTS"] = None - try: - for filename in os.listdir('C:\\Windows\\Fonts'): - if filename.endswith((".ttf","otf")): - self.fontDict[filename] = f"C:\\Windows\\Fonts\\{filename}" - except: - self.fontDict.pop("WINDOWS FONTS") - if len(self.fontDict) > 0: - if self.userFont in self.fontDict: - dpg.add_font(self.fontDict[self.userFont], size=self.userSize, parent=self.font_registry, tag="fntCFPNewFont") - dpg.add_font_chars([0x2013,0x2014,0x2015,0x2017,0x2018,0x2019,0x201A,0x201B,0x201C,0x201D,0x201E,0x2020,0x2021,0x2022,0x2026,0x2030,0x2032,0x2033,0x2039,0x203A,0x203C,0x203E,0x2044,0x204A], parent="fntCFPNewFont") - dpg.bind_font("fntCFPNewFont") - - - def create_font_window(self): - with dpg.window(label="Font Menu", width=400, height=400, show=False, tag="winCFPFontWindow", pos=(int(dpg.get_viewport_width()/2 - 200), int(dpg.get_viewport_height() / 2 - 200))): - with dpg.child_window(autosize_x=True, height=-120): - dpg.add_text("This is the font window. To use it, find your Fonts folder, and put in any .ttf or .otf font, and they will show up here.", wrap=0) - dpg.add_text("Whatever font you choose will be applied to the whole application. Sometimes it's best to close and reopen the application after setting a font, in case things get weird.", wrap=0) - dpg.add_combo(list(self.fontDict.keys()), label="Font Type", callback=lambda:self.build_fonts(), tag="cmbCFPFontType") - dpg.add_input_int(label="Size", default_value=self.userSize, callback=lambda:self.build_fonts(), tag="intCFPSize") - dpg.add_slider_float(default_value=self.userScale, label="Scale", callback=lambda:self.build_fonts(), tag="slideCFPFontScale", min_value=0.0, max_value=2.0, clamped=True) - with dpg.group(horizontal=True): - dpg.add_checkbox(label="Autosize?", tag="chkbxCFPAutosize", default_value=True) - dpg.add_button(label="Change Font", callback=lambda: self.btnBuild()) - - with dpg.child_window(autosize_x=True, height=100): - dpg.add_text("the quick brown fox jumped over the lazy dog", wrap=0) - dpg.add_text("THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG", wrap=0) - with dpg.group(horizontal=True): - dpg.add_button(label="Close", callback=lambda: dpg.configure_item("winCFPFontWindow", show=False)) - dpg.add_button(label="Save", callback=lambda: self.save_fonts()) - dpg.add_button(label="Reset to Default (Your Saves Won't Be Changed)", callback=lambda: dpg.bind_font("DEFAULT") and dpg.set_global_font_scale(1.0)) - - def create_font_menu(self): - self.font_menu = dpg.add_menu(label="Font") - dpg.add_menu_item(label="Edit", callback=lambda: dpg.configure_item("winCFPFontWindow", show=True), parent=self.font_menu) - - def btnBuild(self): - dpg.set_value("chkbxCFPAutosize", True) - self.build_fonts() - dpg.set_value("chkbxCFPAutosize", False) - - def build_fonts(self): - if not dpg.get_value("chkbxCFPAutosize"): return - self.userFont = dpg.get_value("cmbCFPFontType") - self.userSize= dpg.get_value("intCFPSize") - if self.userFont not in self.fontDict: return - if not self.fontDict[self.userFont]: return - try: - if dpg.does_item_exist("fntCFPNewFont"): dpg.delete_item("fntCFPNewFont") - newFont = dpg.add_font(self.fontDict[self.userFont], size=self.userSize, parent=self.font_registry, tag="fntCFPNewFont") - dpg.add_font_range_hint(dpg.mvFontRangeHint_Default, parent="fntCFPNewFont") - dpg.add_font_range_hint(dpg.mvFontRangeHint_Cyrillic, parent="fntCFPNewFont") - dpg.add_font_chars([0x2013,0x2014,0x2015,0x2017,0x2018,0x2019,0x201A,0x201B,0x201C,0x201D,0x201E,0x2020,0x2021,0x2022,0x2026,0x2030,0x2032,0x2033,0x2039,0x203A,0x203C,0x203E,0x2044,0x204A], parent="fntCFPNewFont") - dpg.add_font_range(0x0, 0xF, parent="fntCFPNewFont") - dpg.add_font_range(0x00, 0xFF, parent="fntCFPNewFont") - dpg.add_font_range(0x000, 0xFFF, parent="fntCFPNewFont") - dpg.add_font_range(0x0000, 0xFFFF, parent="fntCFPNewFont") - dpg.bind_font(newFont) - self.userScale = float(dpg.get_value("slideCFPFontScale")) - dpg.set_global_font_scale(self.userScale) - except Exception as e: - print(e) - - def save_fonts(self): - with open('Fonts/USERFONT', 'w') as f: - f.write(str(self.userFont)) - with open('Fonts/USERSIZE', 'w') as f: - f.write(str(self.userSize)) - with open('Fonts/USERSCALE', 'w') as f: - f.write(str(self.userScale)) - dpg.configure_item("winCFPFontWindow", show=False) - -if __name__ == "__main__": - dpg.create_context() - dpg.create_viewport(title='Custom Title', width=1200, height=800) - #from EditThemePlugin import EditThemePlugin - dpg.show_debug() - try: - with dpg.window(tag="main2"): - with dpg.child_window(): - dpg.add_text("This is text") - dpg.add_button(tag="This is a button", label="THIS IS A BUTTON") - dpg.add_checkbox(label="Check Box") - with dpg.child_window(autosize_x=True, autosize_y=True): - with dpg.tab_bar(): - with dpg.tab(label="THIS IS A TAB"): - with dpg.tree_node(label="THIS IS A TREE NODE"): - randListOfStuff = ['THIS', 'IS', 'A', 'LIST'] - dpg.add_combo(randListOfStuff) - dpg.add_listbox(randListOfStuff) - with dpg.viewport_menu_bar(): - with dpg.menu(label="Tools"): - dpg.add_menu_item(label="Show About", callback=lambda:dpg.show_tool(dpg.mvTool_About)) - dpg.add_menu_item(label="Show Metrics", callback=lambda:dpg.show_tool(dpg.mvTool_Metrics)) - dpg.add_menu_item(label="Show Documentation", callback=lambda:dpg.show_tool(dpg.mvTool_Doc)) - dpg.add_menu_item(label="Show Debug", callback=lambda:dpg.show_tool(dpg.mvTool_Debug)) - dpg.add_menu_item(label="Show Style Editor", callback=lambda:dpg.show_tool(dpg.mvTool_Style)) - dpg.add_menu_item(label="Show Font Manager", callback=lambda:dpg.show_tool(dpg.mvTool_Font)) - dpg.add_menu_item(label="Show Item Registry", callback=lambda:dpg.show_tool(dpg.mvTool_ItemRegistry)) - #myEditTheme = EditThemePlugin() - myFonts = ChooseFontsPlugin() - except: - pass - dpg.set_primary_window("main2", True) - dpg.setup_dearpygui() - - dpg.show_viewport() - dpg.start_dearpygui() - dpg.destroy_context() \ No newline at end of file +""" +ChooseFontsPlugin Module + +This plugin provides a font selection interface for DearPyGui applications. +It allows users to browse, preview, and apply custom fonts (.ttf, .otf) to the entire application. +The plugin includes support for system fonts (Windows), user fonts, and font size/scale configuration. +Font selections are persisted to disk for automatic restoration on application restart. +""" + +import dearpygui.dearpygui as dpg +import os +import logging + +logger = logging.getLogger(__name__) + + +class ChooseFontsPlugin(): + """ + A plugin for managing and applying custom fonts in DearPyGui applications. + + This class provides functionality to: + - Discover and load custom fonts from the Fonts directory + - Load system fonts from Windows font directories + - Display a UI for font selection, size, and scale configuration + - Persist font preferences to disk + """ + + def __init__(self): + """ + Initialize the ChooseFontsPlugin. + + Sets up the font registry, creates necessary directories and files, + builds the font library, and creates the UI components. + """ + self.ignore = 'NO CUSTOM FONT, IGNORE' + self.font_registry = dpg.add_font_registry() + self.fontDict = dict() + self.create_folders() + self.create_font_library() + self.create_font_window() + self.create_font_menu() + + def create_folders(self): + """ + Create necessary directories and configuration files for font management. + + Creates a Fonts directory and initializes three configuration files: + - USERFONT: stores the currently selected font name + - USERSIZE: stores the current font size + - USERSCALE: stores the current font scale + """ + if not os.path.exists('Fonts'): + os.mkdir('Fonts') + if not os.path.exists('Fonts/USERFONT'): + with open('Fonts/USERFONT', 'w') as f: + f.write(self.ignore) + if not os.path.exists('Fonts/USERSIZE'): + with open('Fonts/USERSIZE', 'w') as f: + f.write("16") + if not os.path.exists('Fonts/USERSCALE'): + with open('Fonts/USERSCALE', 'w') as f: + f.write("1") + + def create_font_library(self): + """ + Build the font library by scanning for available fonts. + + Loads user font preferences from configuration files and searches for fonts in: + - The Fonts directory (user-provided fonts) + - Windows system fonts (if available) + + Initializes the font registry with found fonts and applies the saved user font settings. + """ + with open('Fonts/USERFONT', 'r') as f: + self.userFont = f.read() + if not self.userFont: + self.userFont = self.ignore + with open('Fonts/USERSIZE', 'r') as f: + try: + self.userSize = int(f.read()) + except ValueError as e: + logger.warning(f"Failed to parse user font size: {e}. Using default size 16.") + self.userSize = 16 + if not self.userSize: + self.userSize = 16 + with open('Fonts/USERSCALE', 'r') as f: + try: + self.userScale = float(f.read()) + except ValueError as e: + logger.warning(f"Failed to parse user font scale: {e}. Using default scale 1.0.") + self.userScale = 1 + if not self.userScale: + self.userScale = 1 + self.fontDict["YOUR FONTS"] = None + for filename in os.listdir('Fonts'): + if filename.lower().endswith((".ttf", ".otf")): + self.fontDict[filename] = f"Fonts/{filename}" + with dpg.font(self.fontDict[filename], 16, parent=self.font_registry) as f: + dpg.add_font_range_hint(dpg.mvFontRangeHint_Default) + dpg.add_font_range_hint(dpg.mvFontRangeHint_Cyrillic) + dpg.add_font_range(0x0, 0xF) + dpg.add_font_range(0x00, 0xFF) + dpg.add_font_range(0x000, 0xFFF) + dpg.add_font_range(0x0000, 0xFFFF) + self.fontDict["WINDOWS FONTS"] = None + try: + for filename in os.listdir('C:\\Windows\\Fonts'): + if filename.lower().endswith((".ttf", ".otf")): + self.fontDict[filename] = f"C:\\Windows\\Fonts\\{filename}" + except FileNotFoundError: + logger.debug("Windows Fonts directory not found. Skipping system fonts.") + self.fontDict.pop("WINDOWS FONTS") + if len(self.fontDict) > 0: + if self.userFont in self.fontDict: + dpg.add_font(self.fontDict[self.userFont], size=self.userSize, parent=self.font_registry, tag="fntCFPNewFont") + dpg.add_font_chars([0x2013, 0x2014, 0x2015, 0x2017, 0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D, 0x201E, 0x2020, 0x2021, 0x2022, 0x2026, 0x2030, 0x2032, 0x2033, 0x2039, 0x203A, 0x203C, 0x203E, 0x2044, 0x204A], parent="fntCFPNewFont") + dpg.bind_font("fntCFPNewFont") + + def create_font_window(self): + """ + Create the UI window for font selection and configuration. + + Provides controls for: + - Font type selection via dropdown + - Font size adjustment + - Font scale slider + - Font preview area + - Autosize checkbox + - Buttons for changing, saving, and resetting fonts + """ + with dpg.window(label="Font Menu", width=400, height=400, show=False, tag="winCFPFontWindow", pos=(int(dpg.get_viewport_width() / 2 - 200), int(dpg.get_viewport_height() / 2 - 200))): + with dpg.child_window(autosize_x=True, height=-120): + dpg.add_text("This is the font window. To use it, find your Fonts folder, and put in any .ttf or .otf font, and they will show up here.", wrap=0) + dpg.add_text("Whatever font you choose will be applied to the whole application. Sometimes it's best to close and reopen the application after setting a font, in case things get weird.", wrap=0) + dpg.add_combo(list(self.fontDict.keys()), label="Font Type", callback=self.build_fonts, tag="cmbCFPFontType") + dpg.add_input_int(label="Size", default_value=self.userSize, callback=self.build_fonts, tag="intCFPSize") + dpg.add_slider_float(default_value=self.userScale, label="Scale", callback=self.build_fonts, tag="slideCFPFontScale", min_value=0.0, max_value=2.0, clamped=True) + with dpg.group(horizontal=True): + dpg.add_checkbox(label="Autosize?", tag="chkbxCFPAutosize", default_value=True) + dpg.add_button(label="Change Font", callback=self.btnBuild) + + with dpg.child_window(autosize_x=True, height=100): + dpg.add_text("the quick brown fox jumped over the lazy dog", wrap=0) + dpg.add_text("THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG", wrap=0) + with dpg.group(horizontal=True): + dpg.add_button(label="Close", callback=self._close_font_window) + dpg.add_button(label="Save", callback=self.save_fonts) + dpg.add_button(label="Reset to Default (Your Saves Won't Be Changed)", callback=self._reset_font_to_default) + + def create_font_menu(self): + """ + Create the Font menu in the menu bar. + + Adds a menu item to open the font selection window. + """ + self.font_menu = dpg.add_menu(label="Font") + dpg.add_menu_item(label="Edit", callback=lambda: dpg.configure_item("winCFPFontWindow", show=True), parent=self.font_menu) + + def btnBuild(self): + """ + Build fonts with autosize temporarily enabled. + + This method temporarily enables autosize to rebuild fonts with new settings, + then disables autosize again to prevent continuous updates. + """ + dpg.set_value("chkbxCFPAutosize", True) + self.build_fonts() + dpg.set_value("chkbxCFPAutosize", False) + + def build_fonts(self): + """ + Build and apply the selected font to the application. + + Only applies fonts if autosize is enabled. Loads the font from disk, + configures font ranges and hints, binds the font to DearPyGui, + and applies the font scale. + + If errors occur during font loading, logs the error for debugging. + """ + if not dpg.get_value("chkbxCFPAutosize"): + return + self.userFont = dpg.get_value("cmbCFPFontType") + self.userSize = dpg.get_value("intCFPSize") + if self.userFont not in self.fontDict: + return + if not self.fontDict[self.userFont]: + return + try: + if dpg.does_item_exist("fntCFPNewFont"): + dpg.delete_item("fntCFPNewFont") + newFont = dpg.add_font(self.fontDict[self.userFont], size=self.userSize, parent=self.font_registry, tag="fntCFPNewFont") + dpg.add_font_range_hint(dpg.mvFontRangeHint_Default, parent="fntCFPNewFont") + dpg.add_font_range_hint(dpg.mvFontRangeHint_Cyrillic, parent="fntCFPNewFont") + dpg.add_font_chars([0x2013, 0x2014, 0x2015, 0x2017, 0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D, 0x201E, 0x2020, 0x2021, 0x2022, 0x2026, 0x2030, 0x2032, 0x2033, 0x2039, 0x203A, 0x203C, 0x203E, 0x2044, 0x204A], parent="fntCFPNewFont") + dpg.add_font_range(0x0, 0xF, parent="fntCFPNewFont") + dpg.add_font_range(0x00, 0xFF, parent="fntCFPNewFont") + dpg.add_font_range(0x000, 0xFFF, parent="fntCFPNewFont") + dpg.add_font_range(0x0000, 0xFFFF, parent="fntCFPNewFont") + dpg.bind_font(newFont) + self.userScale = float(dpg.get_value("slideCFPFontScale")) + dpg.set_global_font_scale(self.userScale) + except Exception as e: + logger.exception("Error building font") + + def save_fonts(self): + """ + Save the current font settings to configuration files. + + Persists the current font selection, size, and scale to disk + so they can be restored on the next application launch. + Closes the font window after saving. + """ + with open('Fonts/USERFONT', 'w') as f: + f.write(str(self.userFont)) + with open('Fonts/USERSIZE', 'w') as f: + f.write(str(self.userSize)) + with open('Fonts/USERSCALE', 'w') as f: + f.write(str(self.userScale)) + dpg.configure_item("winCFPFontWindow", show=False) + + def _close_font_window(self): + """Close the font window.""" + dpg.configure_item("winCFPFontWindow", show=False) + + def _reset_font_to_default(self): + """Reset font to default and reset scale to 1.0.""" + dpg.bind_font("DEFAULT") + dpg.set_global_font_scale(1.0) + + +if __name__ == "__main__": + dpg.create_context() + dpg.create_viewport(title='Custom Title', width=1200, height=800) + dpg.show_debug() + try: + with dpg.window(tag="main2"): + with dpg.child_window(): + dpg.add_text("This is text") + dpg.add_button(tag="This is a button", label="THIS IS A BUTTON") + dpg.add_checkbox(label="Check Box") + with dpg.child_window(autosize_x=True, autosize_y=True): + with dpg.tab_bar(): + with dpg.tab(label="THIS IS A TAB"): + with dpg.tree_node(label="THIS IS A TREE NODE"): + randListOfStuff = ['THIS', 'IS', 'A', 'LIST'] + dpg.add_combo(randListOfStuff) + dpg.add_listbox(randListOfStuff) + with dpg.viewport_menu_bar(): + with dpg.menu(label="Tools"): + dpg.add_menu_item(label="Show About", callback=lambda: dpg.show_tool(dpg.mvTool_About)) + dpg.add_menu_item(label="Show Metrics", callback=lambda: dpg.show_tool(dpg.mvTool_Metrics)) + dpg.add_menu_item(label="Show Documentation", callback=lambda: dpg.show_tool(dpg.mvTool_Doc)) + dpg.add_menu_item(label="Show Debug", callback=lambda: dpg.show_tool(dpg.mvTool_Debug)) + dpg.add_menu_item(label="Show Style Editor", callback=lambda: dpg.show_tool(dpg.mvTool_Style)) + dpg.add_menu_item(label="Show Font Manager", callback=lambda: dpg.show_tool(dpg.mvTool_Font)) + dpg.add_menu_item(label="Show Item Registry", callback=lambda: dpg.show_tool(dpg.mvTool_ItemRegistry)) + myFonts = ChooseFontsPlugin() + except Exception as e: + logger.error(f"Failed to initialize application: {e}") + dpg.set_primary_window("main2", True) + dpg.setup_dearpygui() + + dpg.show_viewport() + dpg.start_dearpygui() + dpg.destroy_context() diff --git a/EditThemePlugin.py b/EditThemePlugin.py index a337b02..f726dea 100644 --- a/EditThemePlugin.py +++ b/EditThemePlugin.py @@ -1,394 +1,362 @@ -import dearpygui.dearpygui as dpg -import configparser -import os - -class EditThemePlugin(): - def __init__(self): - self.confParser = configparser.ConfigParser() - self._create_folders() - self.all_saved_colors() - self._create_UI() - - def _create_folders(self): - if not os.path.exists('themes'): - os.mkdir('themes') - if not os.path.exists('themes/default.ini'): - self.createDefaultTheme() - - def _create_UI(self): - dpg.add_theme(tag="glob") - self.createMenu() - self.loadAll(None, {"file_path_name":'themes/default.ini'}) - - def __str__(self) -> str: - return "glob" - - def createTheme(self): - with dpg.theme_component(dpg.mvAll, parent="glob"): - self.parse_color_operation('dpg.add_theme_color(dpg.[VARGOESHERE], self.getThemeColor("[VARGOESHERE]"), category=dpg.mvThemeCat_Core)') - dpg.bind_theme("glob") - - def createDefaultTheme(self): - self.confParser['Theme'] = {} - self.confParser['Theme']['mvthemecol_text'] = "255.0, 254.99607849121094, 254.99607849121094, 255.0" - self.confParser['Theme']['mvthemecol_tabactive'] = "177.51373291015625, 26.478431701660156, 26.48627471923828, 255.0" - self.confParser['Theme']['mvthemecol_slidergrabactive'] = "249.0, 66.0, 72.062744140625, 255.0" - self.confParser['Theme']['mvthemecol_textdisabled'] = "127.0, 127.0, 127.0, 255.0" - self.confParser['Theme']['mvthemecol_tabunfocused'] = "53.53333282470703, 22.77254867553711, 22.776470184326172, 247.0" - self.confParser['Theme']['mvthemecol_button'] = "123.97647094726562, 0.6823529601097107, 0.6901960968971252, 255.0" - self.confParser['Theme']['mvthemecol_windowbg'] = "12.678431510925293, 12.607843399047852, 12.607843399047852, 239.0" - self.confParser['Theme']['mvthemecol_tabunfocusedactive'] = "107.0, 35.0, 35.00392150878906, 255.0" - self.confParser['Theme']['mvthemecol_buttonhovered'] = "231.04705810546875, 17.870588302612305, 24.937253952026367, 255.0" - self.confParser['Theme']['mvthemecol_childbg'] = "0.0, 0.0, 0.0, 26.764705657958984" - self.confParser['Theme']['mvthemecol_dockingpreview'] = "249.0, 66.0, 66.00784301757812, 178.0" - self.confParser['Theme']['mvthemecol_buttonactive'] = "249.0, 15.0, 15.011764526367188, 255.0" - self.confParser['Theme']['mvthemecol_border'] = "109.0, 109.0, 127.0, 127.0" - self.confParser['Theme']['mvthemecol_dockingemptybg'] = "51.0, 51.0, 51.0, 255.0" - self.confParser['Theme']['mvthemecol_header'] = "249.0, 66.0, 66.00784301757812, 79.0" - self.confParser['Theme']['mvthemecol_popupbg'] = "20.0, 20.0, 20.0, 239.0" - self.confParser['Theme']['mvthemecol_plotlines'] = "155.0, 155.0, 155.0, 255.0" - self.confParser['Theme']['mvthemecol_headerhovered'] = "249.0, 66.0, 66.00784301757812, 204.0" - self.confParser['Theme']['mvthemecol_bordershadow'] = "0.0, 0.0, 0.0, 0.0" - self.confParser['Theme']['mvthemecol_plotlineshovered'] = "255.0, 109.0, 89.0, 255.0" - self.confParser['Theme']['mvthemecol_headeractive'] = "249.0, 66.0, 66.00784301757812, 255.0" - self.confParser['Theme']['mvthemecol_framebg'] = "82.19215393066406, 83.32157135009766, 84.52941131591797, 137.0" - self.confParser['Theme']['mvthemecol_plothistogram'] = "229.0, 178.0, 0.0, 255.0" - self.confParser['Theme']['mvthemecol_separator'] = "109.0, 109.0, 127.0, 127.0" - self.confParser['Theme']['mvthemecol_framebghovered'] = "249.0, 66.0, 66.00784301757812, 102.0" - self.confParser['Theme']['mvthemecol_plothistogramhovered'] = "255.0, 153.0, 0.0, 255.0" - self.confParser['Theme']['mvthemecol_separatorhovered'] = "191.0, 24.713726043701172, 24.713726043701172, 200.0" - self.confParser['Theme']['mvthemecol_framebgactive'] = "255.0, 0.0, 0.0117647061124444, 195.82745361328125" - self.confParser['Theme']['mvthemecol_tableheaderbg'] = "48.0, 48.0, 51.0, 255.0" - self.confParser['Theme']['mvthemecol_separatoractive'] = "191.0, 25.0, 25.007843017578125, 255.0" - self.confParser['Theme']['mvthemecol_titlebg'] = "10.0, 10.0, 10.0, 255.0" - self.confParser['Theme']['mvthemecol_tableborderstrong'] = "79.0, 79.0, 89.0, 255.0" - self.confParser['Theme']['mvthemecol_resizegrip'] = "249.0, 66.0, 66.00784301757812, 51.0" - self.confParser['Theme']['mvthemecol_titlebgactive'] = "122.0, 40.0, 40.00392150878906, 255.0" - self.confParser['Theme']['mvthemecol_tableborderlight'] = "67.67058563232422, 67.67058563232422, 76.07450866699219, 255.0" - self.confParser['Theme']['mvthemecol_resizegriphovered'] = "249.0, 66.0, 66.00784301757812, 170.0" - self.confParser['Theme']['mvthemecol_titlebgcollapsed'] = "0.0, 0.0, 0.0, 130.0" - self.confParser['Theme']['mvthemecol_tablerowbg'] = "0.0, 0.0, 0.0, 0.0" - self.confParser['Theme']['mvthemecol_resizegripactive'] = "236.68235778808594, 36.61176300048828, 36.62352752685547, 242.0" - self.confParser['Theme']['mvthemecol_menubarbg'] = "35.0, 35.0, 35.0, 255.0" - self.confParser['Theme']['mvthemecol_tablerowbgalt'] = "255.0, 255.0, 255.0, 15.0" - self.confParser['Theme']['mvthemecol_tab'] = "147.0, 44.99607849121094, 45.00392150878906, 219.0" - self.confParser['Theme']['mvthemecol_scrollbarbg'] = "5.0, 5.0, 5.0, 135.0" - self.confParser['Theme']['mvthemecol_textselectedbg'] = "249.0, 66.0, 114.52941131591797, 89.0" - self.confParser['Theme']['mvthemecol_tabhovered'] = "249.0, 66.0, 66.00784301757812, 204.0" - self.confParser['Theme']['mvthemecol_scrollbargrab'] = "79.0, 79.0, 79.0, 255.0" - self.confParser['Theme']['mvthemecol_dragdroptarget'] = "255.0, 255.0, 0.0, 229.0" - self.confParser['Theme']['mvthemecol_scrollbargrabhovered'] = "104.0, 104.0, 104.0, 255.0" - self.confParser['Theme']['mvthemecol_navhighlight'] = "249.0, 66.0, 66.00784301757812, 255.0" - self.confParser['Theme']['mvthemecol_scrollbargrabactive'] = "130.0, 130.0, 130.0, 255.0" - self.confParser['Theme']['mvthemecol_navwindowinghighlight'] = "255.0, 255.0, 255.0, 178.0" - self.confParser['Theme']['mvthemecol_checkmark'] = "249.0, 66.0, 66.00784301757812, 255.0" - self.confParser['Theme']['mvthemecol_navwindowingdimbg'] = "204.0, 204.0, 204.0, 51.0" - self.confParser['Theme']['mvthemecol_slidergrab'] = "224.0, 60.99607849121094, 61.007843017578125, 255.0" - self.confParser['Theme']['mvthemecol_modalwindowdimbg'] = "204.0, 204.0, 204.0, 89.0" - with open('themes/default.ini', 'w') as f: - self.confParser.write(f, True) - return - #with dpg.theme() as theme_id: - # with dpg.theme_component(0): - # dpg.add_theme_color(dpg.mvThemeCol_Text , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TextDisabled , (0.50 * 255, 0.50 * 255, 0.50 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_WindowBg , (0.06 * 255, 0.06 * 255, 0.06 * 255, 0.94 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ChildBg , (0.00 * 255, 0.00 * 255, 0.00 * 255, 0.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_PopupBg , (0.08 * 255, 0.08 * 255, 0.08 * 255, 0.94 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_Border , (0.43 * 255, 0.43 * 255, 0.50 * 255, 0.50 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_BorderShadow , (0.00 * 255, 0.00 * 255, 0.00 * 255, 0.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_FrameBg , (0.16 * 255, 0.29 * 255, 0.48 * 255, 0.54 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_FrameBgHovered , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.40 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_FrameBgActive , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.67 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TitleBg , (0.04 * 255, 0.04 * 255, 0.04 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TitleBgActive , (0.16 * 255, 0.29 * 255, 0.48 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TitleBgCollapsed , (0.00 * 255, 0.00 * 255, 0.00 * 255, 0.51 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_MenuBarBg , (0.14 * 255, 0.14 * 255, 0.14 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ScrollbarBg , (0.02 * 255, 0.02 * 255, 0.02 * 255, 0.53 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ScrollbarGrab , (0.31 * 255, 0.31 * 255, 0.31 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ScrollbarGrabHovered , (0.41 * 255, 0.41 * 255, 0.41 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ScrollbarGrabActive , (0.51 * 255, 0.51 * 255, 0.51 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_CheckMark , (0.26 * 255, 0.59 * 255, 0.98 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_SliderGrab , (0.24 * 255, 0.52 * 255, 0.88 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_SliderGrabActive , (0.26 * 255, 0.59 * 255, 0.98 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_Button , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.40 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered , (0.26 * 255, 0.59 * 255, 0.98 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ButtonActive , (0.06 * 255, 0.53 * 255, 0.98 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_Header , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.31 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_HeaderHovered , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.80 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_HeaderActive , (0.26 * 255, 0.59 * 255, 0.98 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_Separator , (0.43 * 255, 0.43 * 255, 0.50 * 255, 0.50 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_SeparatorHovered , (0.10 * 255, 0.40 * 255, 0.75 * 255, 0.78 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_SeparatorActive , (0.10 * 255, 0.40 * 255, 0.75 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ResizeGrip , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.20 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ResizeGripHovered , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.67 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ResizeGripActive , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.95 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_Tab , (0.18 * 255, 0.35 * 255, 0.58 * 255, 0.86 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TabHovered , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.80 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TabActive , (0.20 * 255, 0.41 * 255, 0.68 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TabUnfocused , (0.07 * 255, 0.10 * 255, 0.15 * 255, 0.97 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TabUnfocusedActive , (0.14 * 255, 0.26 * 255, 0.42 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_DockingPreview , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.70 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_DockingEmptyBg , (0.20 * 255, 0.20 * 255, 0.20 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_PlotLines , (0.61 * 255, 0.61 * 255, 0.61 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_PlotLinesHovered , (1.00 * 255, 0.43 * 255, 0.35 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_PlotHistogram , (0.90 * 255, 0.70 * 255, 0.00 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_PlotHistogramHovered , (1.00 * 255, 0.60 * 255, 0.00 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TableHeaderBg , (0.19 * 255, 0.19 * 255, 0.20 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TableBorderStrong , (0.31 * 255, 0.31 * 255, 0.35 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TableBorderLight , (0.23 * 255, 0.23 * 255, 0.25 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TableRowBg , (0.00 * 255, 0.00 * 255, 0.00 * 255, 0.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TableRowBgAlt , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.06 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_TextSelectedBg , (0.26 * 255, 0.59 * 255, 0.98 * 255, 0.35 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_DragDropTarget , (1.00 * 255, 1.00 * 255, 0.00 * 255, 0.90 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_NavHighlight , (0.26 * 255, 0.59 * 255, 0.98 * 255, 1.00 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_NavWindowingHighlight , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.70 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_NavWindowingDimBg , (0.80 * 255, 0.80 * 255, 0.80 * 255, 0.20 * 255)) - # dpg.add_theme_color(dpg.mvThemeCol_ModalWindowDimBg , (0.80 * 255, 0.80 * 255, 0.80 * 255, 0.35 * 255)) - # dpg.add_theme_color(dpg.mvPlotCol_FrameBg , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.07 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_PlotBg , (0.00 * 255, 0.00 * 255, 0.00 * 255, 0.50 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_PlotBorder , (0.43 * 255, 0.43 * 255, 0.50 * 255, 0.50 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_LegendBg , (0.08 * 255, 0.08 * 255, 0.08 * 255, 0.94 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_LegendBorder , (0.43 * 255, 0.43 * 255, 0.50 * 255, 0.50 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_LegendText , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_TitleText , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_InlayText , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_XAxis , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_XAxisGrid , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.25 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_YAxis , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_YAxisGrid , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.25 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_YAxis2 , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_YAxisGrid2 , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.25 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_YAxis3 , (1.00 * 255, 1.00 * 255, 1.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_YAxisGrid3 , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.25 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_Selection , (1.00 * 255, 0.60 * 255, 0.00 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_Query , (0.00 * 255, 1.00 * 255, 0.44 * 255, 1.00 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvPlotCol_Crosshairs , (1.00 * 255, 1.00 * 255, 1.00 * 255, 0.50 * 255), category=dpg.mvThemeCat_Plots) - # dpg.add_theme_color(dpg.mvNodeCol_NodeBackground, (50, 50, 50, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_NodeBackgroundHovered, (75, 75, 75, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_NodeBackgroundSelected, (75, 75, 75, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_NodeOutline, (100, 100, 100, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_TitleBar, (41, 74, 122, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_TitleBarHovered, (66, 150, 250, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_TitleBarSelected, (66, 150, 250, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_Link, (61, 133, 224, 200), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_LinkHovered, (66, 150, 250, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_LinkSelected, (66, 150, 250, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_Pin, (53, 150, 250, 180), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_PinHovered, (53, 150, 250, 255), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_BoxSelector, (61, 133, 224, 30), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_BoxSelectorOutline, (61, 133, 224, 150), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_GridBackground, (40, 40, 50, 200), category=dpg.mvThemeCat_Nodes) - # dpg.add_theme_color(dpg.mvNodeCol_GridLine, (200, 200, 200, 40), category=dpg.mvThemeCat_Nodes) - #return theme_id - - def createMenu(self): - pluginMenu = dpg.add_menu(label="Theme") - self.loadTheme() - self.saveTheme() - self.editTheme() - dpg.add_menu_item(label="Configure Theme", callback=lambda: dpg.configure_item('editThemeWindow', show=True), parent=pluginMenu) - dpg.add_menu_item(label="Load Theme", callback=lambda: dpg.configure_item("loadThemeFileSelector", show=True), parent=pluginMenu) - dpg.add_menu_item(label="Save Theme", callback=lambda: dpg.configure_item("saveThemeFileSelector", show=True), parent=pluginMenu) - - def editTheme(self): - dpg.add_window(tag="editThemeWindow", show=False, autosize=True, no_title_bar=True, max_size=[1080,720]) - dpg.add_button(parent="editThemeWindow", label="Close", callback=lambda: dpg.configure_item("editThemeWindow", show=False)) - dpg.add_button(label="Load Theme", callback=lambda: dpg.configure_item("loadThemeFileSelector", show=True), parent="editThemeWindow") - dpg.add_button(label="Save Theme", callback=lambda: dpg.configure_item("saveThemeFileSelector", show=True), parent="editThemeWindow") - myEditVar = """dpg.add_color_edit(self.getThemeColor("[VARGOESHERE]"), source=self.confParser['Theme']["[VARGOESHERE]"], display_type=dpg.mvColorEdit_uint8, alpha_bar=True, alpha_preview=dpg.mvColorEdit_AlphaPreviewHalf, tag="colEdit[VARGOESHERE]", parent='editThemeWindow', label='[VARGOESHERE]', callback=lambda: dpg.add_theme_color(dpg.[VARGOESHERE], dpg.get_value("colEdit[VARGOESHERE]"), parent=dpg.add_theme_component(dpg.mvAll, parent="glob"))- 0 if not dpg.bind_theme("glob") else 0)""" - #dpg.add_color_edit(self.getThemeColor("[VARGOESHERE]"), source=self.confParser['Theme']["[VARGOESHERE]"], display_type=dpg.mvColorEdit_uint8, alpha_bar=True, alpha_preview=dpg.mvColorEdit_AlphaPreviewHalf,tag="colEdit[VARGOESHERE]", parent='editThemeWindow', label='[VARGOESHERE]', callback=lambda: dpg.add_theme_color(dpg.[VARGOESHERE], dpg.get_value("colEdit[VARGOESHERE]"), parent=dpg.add_theme_component(dpg.mvAll, parent="glob"))- 0 if not dpg.bind_theme("glob") else 0) - self.parse_color_operation(myEditVar) - - def loadAll(self, a=None, sentFileDict=None): - try: - #self.confParser = configparser.ConfigParser() - self.confParser.read(sentFileDict["file_path_name"]) - dpg.delete_item("editThemeWindow") - self.createTheme() - self.editTheme() - #with open("newFile.ini", 'w') as f: self.confParser.write(f) - except: - return - - - - def loadTheme(self): - try: - with dpg.file_dialog(callback=self.loadAll, directory_selector=False, width=700, height=400, default_path="themes", default_filename="default.ini", show=False, tag="loadThemeFileSelector", cancel_callback=doNothing): - dpg.add_file_extension(".ini") - pass - except: - return - - def saveTheme(self): - try: - with dpg.file_dialog(default_path="themes", default_filename=".ini",callback=self.saveAll, directory_selector=False, width=700, height=400, show=False, tag="saveThemeFileSelector", cancel_callback=doNothing): - dpg.add_file_extension(".ini") - dpg.add_button(label="save") - except: - return - pass - return - - def saveAll(self, a, b): - #print(f"{self}, {a}, {b}") - if not "file_path_name" in b: - return - try: - #self.confParser['Theme'] = {} - #print(a, b, c) - saveConf = "self.confParser['Theme']['[VARGOESHERE]']=str(dpg.get_value('colEdit[VARGOESHERE]'))" - self.parse_color_operation(saveConf) - with open(b["file_path_name"], 'w') as f: self.confParser.write(f,True) - except: - return - - def getThemeColor(self, themeCol): - itm = self.confParser['Theme'][themeCol] - #print(type(itm)) - itm = itm.removeprefix('[') - itm = itm.removesuffix(']') - thrIntAsStr = itm - #print(thrIntAsStr) - a = thrIntAsStr.split(',') - #print(a[0]) - #print((int(float(a[0].strip())),int(float(a[1].strip())),int(float(a[2].strip())),int(float(a[3].strip())))) - return (int(float(a[0].strip())),int(float(a[1].strip())),int(float(a[2].strip())),int(float(a[3].strip()))) - #c = dict() - #i = 0 - #for a in thrIntAsStr.split(','): - # b = a.split(' * ') - # #print(b) - # c[str(i)] = float(b[0].strip()) * float(b[1].strip()) - # i += 1 - ##print(c) - #self.confParser['Theme'][themeCol] = f'{int(c["0"])},{int(c["1"])},{int(c["2"])},{int(c["3"])}' - #return (int(c["0"]),int(c["1"]),int(c["2"]),int(c["3"])) - #self.confParser['Theme']['mvThemeCol_ModalWindowDimBg']=dpg.get_value('colEditmvThemeCol_ModalWindowDimBg') - - - def parse_color_operation(self, operation: str): - """Write operation like this: - - Args: - operation (str): 'dpg.add_theme_color(dpg.[VARGOESHERE], self.getThemeColor("[VARGOESHERE]"), category=dpg.mvThemeCat_Core)' - """ - for c in self.theColors: - newVar = operation - newVar = newVar.replace("[VARGOESHERE]", c) - try: - #print(newVar) - exec(newVar) - except: - #exec(f"#print(dpg.{c})") - pass##print(newVar) - return - - - def all_saved_colors(self): - self.theColors = [ - "mvThemeCol_Text", - "mvThemeCol_TextSelectedBg", - "mvThemeCol_TextDisabled", - "mvThemeCol_TabActive", - "mvThemeCol_TabUnfocused", - "mvThemeCol_TabUnfocusedActive", - "mvThemeCol_TabHovered", - "mvThemeCol_Tab", - "mvThemeCol_Button", - "mvThemeCol_ButtonHovered", - "mvThemeCol_ButtonActive", - "mvThemeCol_WindowBg", - "mvThemeCol_ChildBg", - "mvThemeCol_PopupBg", - "mvThemeCol_FrameBg", - "mvThemeCol_FrameBgHovered", - "mvThemeCol_FrameBgActive", - "mvThemeCol_TitleBg", - "mvThemeCol_TitleBgActive", - "mvThemeCol_TitleBgCollapsed", - "mvThemeCol_MenuBarBg", - "mvThemeCol_DockingEmptyBg", - "mvThemeCol_ScrollbarBg", - "mvThemeCol_ResizeGripActive", - "mvThemeCol_ScrollbarGrab", - "mvThemeCol_ScrollbarGrabHovered", - "mvThemeCol_ScrollbarGrabActive", - "mvThemeCol_Border", - "mvThemeCol_BorderShadow", - "mvThemeCol_SliderGrabActive", - "mvThemeCol_DockingPreview", - "mvThemeCol_Header", - "mvThemeCol_PlotLines", - "mvThemeCol_HeaderHovered", - "mvThemeCol_PlotLinesHovered", - "mvThemeCol_HeaderActive", - "mvThemeCol_PlotHistogram", - "mvThemeCol_Separator", - "mvThemeCol_PlotHistogramHovered", - "mvThemeCol_SeparatorHovered", - "mvThemeCol_TableHeaderBg", - "mvThemeCol_SeparatorActive", - "mvThemeCol_TableBorderStrong", - "mvThemeCol_ResizeGrip", - "mvThemeCol_TableBorderLight", - "mvThemeCol_ResizeGripHovered", - "mvThemeCol_TableRowBg", - "mvThemeCol_TableRowBgAlt", - "mvThemeCol_DragDropTarget", - "mvThemeCol_NavHighlight", - "mvThemeCol_NavWindowingHighlight", - "mvThemeCol_CheckMark", - "mvThemeCol_NavWindowingDimBg", - "mvThemeCol_SliderGrab", - "mvThemeCol_ModalWindowDimBg", - ] - -def doNothing(*args): - return - -if __name__=="__main__": - dpg.create_context() - dpg.create_viewport(title='Custom Title', width=1200, height=800) - with dpg.theme() as global_theme: - with dpg.theme_component(dpg.mvAll) as gtc: - dpg.add_theme_color(dpg.mvThemeCol_Text, (0,0,0,255), category=dpg.mvThemeCat_Core, tag="globcolor") - dpg.bind_theme(global_theme) - #dpg.add_theme(tag="glob") - def bind():dpg.add_theme_color(dpg.mvThemeCol_Text, dpg.get_value("ve"), parent=dpg.add_theme_component(dpg.mvAll, parent="glob"));dpg.bind_theme("glob") - with dpg.window(tag="main", show=False): - dpg.add_color_edit(parent="main",tag='ve', callback=bind) - dpg.add_text(dpg.get_item_info("main"), wrap=0) - dpg.add_text(dpg.get_app_configuration(), wrap=0) - with dpg.value_registry(): - dpg.add_color_value(source="ve", tag="vete") - with dpg.window(tag="main2"): - with dpg.child_window(): - dpg.add_text("This is text") - dpg.add_button(tag="This is a button", label="THIS IS A BUTTON") - dpg.add_checkbox(label="Check Box") - with dpg.child_window(autosize_x=True, autosize_y=True): - with dpg.tab_bar(): - with dpg.tab(label="THIS IS A TAB"): - with dpg.tree_node(label="THIS IS A TREE NODE"): - randListOfStuff = ['THIS', 'IS', 'A', 'LIST'] - dpg.add_combo(randListOfStuff) - dpg.add_listbox(randListOfStuff) - - with dpg.viewport_menu_bar(): - with dpg.menu(label="Tools"): - dpg.add_menu_item(label="Show About", callback=lambda:dpg.show_tool(dpg.mvTool_About)) - dpg.add_menu_item(label="Show Metrics", callback=lambda:dpg.show_tool(dpg.mvTool_Metrics)) - dpg.add_menu_item(label="Show Documentation", callback=lambda:dpg.show_tool(dpg.mvTool_Doc)) - dpg.add_menu_item(label="Show Debug", callback=lambda:dpg.show_tool(dpg.mvTool_Debug)) - dpg.add_menu_item(label="Show Style Editor", callback=lambda:dpg.show_tool(dpg.mvTool_Style)) - dpg.add_menu_item(label="Show Font Manager", callback=lambda:dpg.show_tool(dpg.mvTool_Font)) - dpg.add_menu_item(label="Show Item Registry", callback=lambda:dpg.show_tool(dpg.mvTool_ItemRegistry)) - EditThemePlugin() - dpg.set_primary_window("main2", True) - dpg.setup_dearpygui() - - dpg.show_viewport() - dpg.start_dearpygui() - dpg.destroy_context() \ No newline at end of file +""" +EditThemePlugin for DearPyGui + +A plugin that allows users to create, edit, save, and load custom themes +for DearPyGui applications. Themes are stored as .ini files in the themes/ folder. +""" + +import dearpygui.dearpygui as dpg +import configparser +import os + + +class EditThemePlugin(): + """A DearPyGui plugin for editing and managing themes.""" + + def __init__(self): + """Initialize the theme plugin and create the UI.""" + self.confParser = configparser.ConfigParser() + self._create_folders() + self.all_saved_colors() + self._create_UI() + + def _create_folders(self): + """Create the themes folder and default theme file if they don't exist.""" + if not os.path.exists('themes'): + os.mkdir('themes') + if not os.path.exists('themes/default.ini'): + self.createDefaultTheme() + + def _create_UI(self): + """Create the theme UI and load the default theme.""" + dpg.add_theme(tag="glob") + self.createMenu() + self.loadAll(None, {"file_path_name": 'themes/default.ini'}) + + def __str__(self) -> str: + """Return the theme tag identifier.""" + return "glob" + + def createTheme(self): + """Create and bind a theme with all configured colors.""" + with dpg.theme_component(dpg.mvAll, parent="glob"): + self._add_theme_colors() + dpg.bind_theme("glob") + + def createDefaultTheme(self): + """Create a default theme configuration and save it to themes/default.ini.""" + self.confParser['Theme'] = {} + self.confParser['Theme']['mvthemecol_text'] = "255.0, 254.99607849121094, 254.99607849121094, 255.0" + self.confParser['Theme']['mvthemecol_tabactive'] = "177.51373291015625, 26.478431701660156, 26.48627471923828, 255.0" + self.confParser['Theme']['mvthemecol_slidergrabactive'] = "249.0, 66.0, 72.062744140625, 255.0" + self.confParser['Theme']['mvthemecol_textdisabled'] = "127.0, 127.0, 127.0, 255.0" + self.confParser['Theme']['mvthemecol_tabunfocused'] = "53.53333282470703, 22.77254867553711, 22.776470184326172, 247.0" + self.confParser['Theme']['mvthemecol_button'] = "123.97647094726562, 0.6823529601097107, 0.6901960968971252, 255.0" + self.confParser['Theme']['mvthemecol_windowbg'] = "12.678431510925293, 12.607843399047852, 12.607843399047852, 239.0" + self.confParser['Theme']['mvthemecol_tabunfocusedactive'] = "107.0, 35.0, 35.00392150878906, 255.0" + self.confParser['Theme']['mvthemecol_buttonhovered'] = "231.04705810546875, 17.870588302612305, 24.937253952026367, 255.0" + self.confParser['Theme']['mvthemecol_childbg'] = "0.0, 0.0, 0.0, 26.764705657958984" + self.confParser['Theme']['mvthemecol_dockingpreview'] = "249.0, 66.0, 66.00784301757812, 178.0" + self.confParser['Theme']['mvthemecol_buttonactive'] = "249.0, 15.0, 15.011764526367188, 255.0" + self.confParser['Theme']['mvthemecol_border'] = "109.0, 109.0, 127.0, 127.0" + self.confParser['Theme']['mvthemecol_dockingemptybg'] = "51.0, 51.0, 51.0, 255.0" + self.confParser['Theme']['mvthemecol_header'] = "249.0, 66.0, 66.00784301757812, 79.0" + self.confParser['Theme']['mvthemecol_popupbg'] = "20.0, 20.0, 20.0, 239.0" + self.confParser['Theme']['mvthemecol_plotlines'] = "155.0, 155.0, 155.0, 255.0" + self.confParser['Theme']['mvthemecol_headerhovered'] = "249.0, 66.0, 66.00784301757812, 204.0" + self.confParser['Theme']['mvthemecol_bordershadow'] = "0.0, 0.0, 0.0, 0.0" + self.confParser['Theme']['mvthemecol_plotlineshovered'] = "255.0, 109.0, 89.0, 255.0" + self.confParser['Theme']['mvthemecol_headeractive'] = "249.0, 66.0, 66.00784301757812, 255.0" + self.confParser['Theme']['mvthemecol_framebg'] = "82.19215393066406, 83.32157135009766, 84.52941131591797, 137.0" + self.confParser['Theme']['mvthemecol_plothistogram'] = "229.0, 178.0, 0.0, 255.0" + self.confParser['Theme']['mvthemecol_separator'] = "109.0, 109.0, 127.0, 127.0" + self.confParser['Theme']['mvthemecol_framebghovered'] = "249.0, 66.0, 66.00784301757812, 102.0" + self.confParser['Theme']['mvthemecol_plothistogramhovered'] = "255.0, 153.0, 0.0, 255.0" + self.confParser['Theme']['mvthemecol_separatorhovered'] = "191.0, 24.713726043701172, 24.713726043701172, 200.0" + self.confParser['Theme']['mvthemecol_framebgactive'] = "255.0, 0.0, 0.0117647061124444, 195.82745361328125" + self.confParser['Theme']['mvthemecol_tableheaderbg'] = "48.0, 48.0, 51.0, 255.0" + self.confParser['Theme']['mvthemecol_separatoractive'] = "191.0, 25.0, 25.007843017578125, 255.0" + self.confParser['Theme']['mvthemecol_titlebg'] = "10.0, 10.0, 10.0, 255.0" + self.confParser['Theme']['mvthemecol_tableborderstrong'] = "79.0, 79.0, 89.0, 255.0" + self.confParser['Theme']['mvthemecol_resizegrip'] = "249.0, 66.0, 66.00784301757812, 51.0" + self.confParser['Theme']['mvthemecol_titlebgactive'] = "122.0, 40.0, 40.00392150878906, 255.0" + self.confParser['Theme']['mvthemecol_tableborderlight'] = "67.67058563232422, 67.67058563232422, 76.07450866699219, 255.0" + self.confParser['Theme']['mvthemecol_resizegriphovered'] = "249.0, 66.0, 66.00784301757812, 170.0" + self.confParser['Theme']['mvthemecol_titlebgcollapsed'] = "0.0, 0.0, 0.0, 130.0" + self.confParser['Theme']['mvthemecol_tablerowbg'] = "0.0, 0.0, 0.0, 0.0" + self.confParser['Theme']['mvthemecol_resizegripactive'] = "236.68235778808594, 36.61176300048828, 36.62352752685547, 242.0" + self.confParser['Theme']['mvthemecol_menubarbg'] = "35.0, 35.0, 35.0, 255.0" + self.confParser['Theme']['mvthemecol_tablerowbgalt'] = "255.0, 255.0, 255.0, 15.0" + self.confParser['Theme']['mvthemecol_tab'] = "147.0, 44.99607849121094, 45.00392150878906, 219.0" + self.confParser['Theme']['mvthemecol_scrollbarbg'] = "5.0, 5.0, 5.0, 135.0" + self.confParser['Theme']['mvthemecol_textselectedbg'] = "249.0, 66.0, 114.52941131591797, 89.0" + self.confParser['Theme']['mvthemecol_tabhovered'] = "249.0, 66.0, 66.00784301757812, 204.0" + self.confParser['Theme']['mvthemecol_scrollbargrab'] = "79.0, 79.0, 79.0, 255.0" + self.confParser['Theme']['mvthemecol_dragdroptarget'] = "255.0, 255.0, 0.0, 229.0" + self.confParser['Theme']['mvthemecol_scrollbargrabhovered'] = "104.0, 104.0, 104.0, 255.0" + self.confParser['Theme']['mvthemecol_navhighlight'] = "249.0, 66.0, 66.00784301757812, 255.0" + self.confParser['Theme']['mvthemecol_scrollbargrabactive'] = "130.0, 130.0, 130.0, 255.0" + self.confParser['Theme']['mvthemecol_navwindowinghighlight'] = "255.0, 255.0, 255.0, 178.0" + self.confParser['Theme']['mvthemecol_checkmark'] = "249.0, 66.0, 66.00784301757812, 255.0" + self.confParser['Theme']['mvthemecol_navwindowingdimbg'] = "204.0, 204.0, 204.0, 51.0" + self.confParser['Theme']['mvthemecol_slidergrab'] = "224.0, 60.99607849121094, 61.007843017578125, 255.0" + self.confParser['Theme']['mvthemecol_modalwindowdimbg'] = "204.0, 204.0, 204.0, 89.0" + with open('themes/default.ini', 'w') as f: + self.confParser.write(f, True) + return + + def createMenu(self): + pluginMenu = dpg.add_menu(label="Theme") + self.loadTheme() + self.saveTheme() + self.editTheme() + dpg.add_menu_item(label="Configure Theme", callback=lambda: dpg.configure_item('editThemeWindow', show=True), parent=pluginMenu) + dpg.add_menu_item(label="Load Theme", callback=lambda: dpg.configure_item("loadThemeFileSelector", show=True), parent=pluginMenu) + dpg.add_menu_item(label="Save Theme", callback=lambda: dpg.configure_item("saveThemeFileSelector", show=True), parent=pluginMenu) + + def editTheme(self): + """Create the theme editor window with color editing controls for all theme colors.""" + dpg.add_window(tag="editThemeWindow", show=False, autosize=True, no_title_bar=True, max_size=[1080, 720]) + dpg.add_button(parent="editThemeWindow", label="Close", callback=self._close_edit_window) + dpg.add_button(label="Load Theme", callback=self._show_load_dialog, parent="editThemeWindow") + dpg.add_button(label="Save Theme", callback=self._show_save_dialog, parent="editThemeWindow") + + # Create color editors for each theme color + for color_name in self.theColors: + self._create_color_editor(color_name) + + def loadAll(self, a=None, sentFileDict=None): + try: + self.confParser.read(sentFileDict["file_path_name"]) + dpg.delete_item("editThemeWindow") + self.createTheme() + self.editTheme() + except Exception as e: + print(f"Error loading theme: {e}") + return + + + + def loadTheme(self): + try: + with dpg.file_dialog(callback=self.loadAll, directory_selector=False, width=700, height=400, default_path="themes", default_filename="default.ini", show=False, tag="loadThemeFileSelector", cancel_callback=doNothing): + dpg.add_file_extension(".ini") + except Exception as e: + print(f"Error creating load theme dialog: {e}") + return + + def saveTheme(self): + try: + with dpg.file_dialog(default_path="themes", default_filename=".ini", callback=self.saveAll, directory_selector=False, width=700, height=400, show=False, tag="saveThemeFileSelector", cancel_callback=doNothing): + dpg.add_file_extension(".ini") + dpg.add_button(label="save") + except Exception as e: + print(f"Error creating save theme dialog: {e}") + return + + def saveAll(self, a, b): + if "file_path_name" not in b: + return + try: + self._save_theme_colors() + with open(b["file_path_name"], 'w') as f: + self.confParser.write(f, True) + except Exception as e: + print(f"Error saving theme: {e}") + return + + def getThemeColor(self, themeCol): + try: + itm = self.confParser['Theme'][themeCol] + itm = itm.removeprefix('[') + itm = itm.removesuffix(']') + thrIntAsStr = itm + a = thrIntAsStr.split(',') + return (int(float(a[0].strip())), int(float(a[1].strip())), int(float(a[2].strip())), int(float(a[3].strip()))) + except (KeyError, ValueError, IndexError) as e: + print(f"Error parsing theme color for {themeCol}: {e}") + return (255, 255, 255, 255) + + + def _add_theme_colors(self): + """Add theme colors using DearPyGui API directly, avoiding exec().""" + for color_name in self.theColors: + try: + color_constant = getattr(dpg, color_name, None) + if color_constant is None: + continue + color_value = self.getThemeColor(color_name) + dpg.add_theme_color(color_constant, color_value, category=dpg.mvThemeCat_Core) + except Exception as e: + print(f"Error adding theme color for {color_name}: {e}") + + def _save_theme_colors(self): + """Save current theme colors from color edit widgets to config.""" + for color_name in self.theColors: + try: + widget_id = f"colEdit{color_name}" + if dpg.does_item_exist(widget_id): + value = dpg.get_value(widget_id) + if "Theme" not in self.confParser: + self.confParser["Theme"] = {} + self.confParser["Theme"][color_name] = str(value) + except Exception as e: + print(f"Error saving theme color for {color_name}: {e}") + + def _create_color_editor(self, color_name): + """Create a color editor widget for a specific theme color.""" + try: + color_value = self.getThemeColor(color_name) + tag = f"colEdit{color_name}" + + def on_color_change(): + """Callback when color is changed.""" + try: + color_constant = getattr(dpg, color_name, None) + if color_constant is None: + return + new_value = dpg.get_value(tag) + dpg.add_theme_color( + color_constant, + new_value, + parent=dpg.add_theme_component(dpg.mvAll, parent="glob") + ) + dpg.bind_theme("glob") + except Exception as e: + print(f"Error updating color {color_name}: {e}") + + dpg.add_color_edit( + color_value, + source=self.confParser['Theme'].get(color_name, "255, 255, 255, 255"), + display_type=dpg.mvColorEdit_uint8, + alpha_bar=True, + alpha_preview=dpg.mvColorEdit_AlphaPreviewHalf, + tag=tag, + parent='editThemeWindow', + label=color_name, + callback=on_color_change + ) + except Exception as e: + print(f"Error creating color editor for {color_name}: {e}") + + def _close_edit_window(self): + """Close the edit theme window.""" + dpg.configure_item("editThemeWindow", show=False) + + def _show_load_dialog(self): + """Show the load theme file dialog.""" + dpg.configure_item("loadThemeFileSelector", show=True) + + def _show_save_dialog(self): + """Show the save theme file dialog.""" + dpg.configure_item("saveThemeFileSelector", show=True) + + + def all_saved_colors(self): + self.theColors = [ + "mvThemeCol_Text", + "mvThemeCol_TextSelectedBg", + "mvThemeCol_TextDisabled", + "mvThemeCol_TabActive", + "mvThemeCol_TabUnfocused", + "mvThemeCol_TabUnfocusedActive", + "mvThemeCol_TabHovered", + "mvThemeCol_Tab", + "mvThemeCol_Button", + "mvThemeCol_ButtonHovered", + "mvThemeCol_ButtonActive", + "mvThemeCol_WindowBg", + "mvThemeCol_ChildBg", + "mvThemeCol_PopupBg", + "mvThemeCol_FrameBg", + "mvThemeCol_FrameBgHovered", + "mvThemeCol_FrameBgActive", + "mvThemeCol_TitleBg", + "mvThemeCol_TitleBgActive", + "mvThemeCol_TitleBgCollapsed", + "mvThemeCol_MenuBarBg", + "mvThemeCol_DockingEmptyBg", + "mvThemeCol_ScrollbarBg", + "mvThemeCol_ResizeGripActive", + "mvThemeCol_ScrollbarGrab", + "mvThemeCol_ScrollbarGrabHovered", + "mvThemeCol_ScrollbarGrabActive", + "mvThemeCol_Border", + "mvThemeCol_BorderShadow", + "mvThemeCol_SliderGrabActive", + "mvThemeCol_DockingPreview", + "mvThemeCol_Header", + "mvThemeCol_PlotLines", + "mvThemeCol_HeaderHovered", + "mvThemeCol_PlotLinesHovered", + "mvThemeCol_HeaderActive", + "mvThemeCol_PlotHistogram", + "mvThemeCol_Separator", + "mvThemeCol_PlotHistogramHovered", + "mvThemeCol_SeparatorHovered", + "mvThemeCol_TableHeaderBg", + "mvThemeCol_SeparatorActive", + "mvThemeCol_TableBorderStrong", + "mvThemeCol_ResizeGrip", + "mvThemeCol_TableBorderLight", + "mvThemeCol_ResizeGripHovered", + "mvThemeCol_TableRowBg", + "mvThemeCol_TableRowBgAlt", + "mvThemeCol_DragDropTarget", + "mvThemeCol_NavHighlight", + "mvThemeCol_NavWindowingHighlight", + "mvThemeCol_CheckMark", + "mvThemeCol_NavWindowingDimBg", + "mvThemeCol_SliderGrab", + "mvThemeCol_ModalWindowDimBg", + ] + +def doNothing(*args): + """Placeholder callback function for canceled file dialogs.""" + return + +if __name__=="__main__": + dpg.create_context() + dpg.create_viewport(title='Custom Title', width=1200, height=800) + with dpg.theme() as global_theme: + with dpg.theme_component(dpg.mvAll) as gtc: + dpg.add_theme_color(dpg.mvThemeCol_Text, (0,0,0,255), category=dpg.mvThemeCat_Core, tag="globcolor") + dpg.bind_theme(global_theme) + def bind(): + dpg.add_theme_color(dpg.mvThemeCol_Text, dpg.get_value("ve"), parent=dpg.add_theme_component(dpg.mvAll, parent="glob")) + dpg.bind_theme("glob") + with dpg.window(tag="main", show=False): + dpg.add_color_edit(parent="main",tag='ve', callback=bind) + dpg.add_text(dpg.get_item_info("main"), wrap=0) + dpg.add_text(dpg.get_app_configuration(), wrap=0) + with dpg.value_registry(): + dpg.add_color_value(source="ve", tag="vete") + with dpg.window(tag="main2"): + with dpg.child_window(): + dpg.add_text("This is text") + dpg.add_button(tag="This is a button", label="THIS IS A BUTTON") + dpg.add_checkbox(label="Check Box") + with dpg.child_window(autosize_x=True, autosize_y=True): + with dpg.tab_bar(): + with dpg.tab(label="THIS IS A TAB"): + with dpg.tree_node(label="THIS IS A TREE NODE"): + randListOfStuff = ['THIS', 'IS', 'A', 'LIST'] + dpg.add_combo(randListOfStuff) + dpg.add_listbox(randListOfStuff) + + with dpg.viewport_menu_bar(): + with dpg.menu(label="Tools"): + dpg.add_menu_item(label="Show About", callback=lambda:dpg.show_tool(dpg.mvTool_About)) + dpg.add_menu_item(label="Show Metrics", callback=lambda:dpg.show_tool(dpg.mvTool_Metrics)) + dpg.add_menu_item(label="Show Documentation", callback=lambda:dpg.show_tool(dpg.mvTool_Doc)) + dpg.add_menu_item(label="Show Debug", callback=lambda:dpg.show_tool(dpg.mvTool_Debug)) + dpg.add_menu_item(label="Show Style Editor", callback=lambda:dpg.show_tool(dpg.mvTool_Style)) + dpg.add_menu_item(label="Show Font Manager", callback=lambda:dpg.show_tool(dpg.mvTool_Font)) + dpg.add_menu_item(label="Show Item Registry", callback=lambda:dpg.show_tool(dpg.mvTool_ItemRegistry)) + EditThemePlugin() + dpg.set_primary_window("main2", True) + dpg.setup_dearpygui() + + dpg.show_viewport() + dpg.start_dearpygui() + dpg.destroy_context() \ No newline at end of file diff --git a/README.md b/README.md index 2ef4b0b..e67e52e 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,103 @@ -# DearPyGui_EditThemePlugin -Adds a viewport menu that allows the user to create a theme in dearpygui, save the theme to a .ini file, and load themes from a .ini file. +# DearPyGui Edit Theme Plugin -You can take a look at the code to see how it works. There's a small demo in this that shows a brief demonstration of different objects. +A powerful theme editor and font selector plugin for [DearPyGui](https://github.com/hoffstadt/DearPyGui) that allows users to customize their application's appearance in real-time. -To use EditThemePlugin, all you have to do is load the class, then call the class in a menu. +## Features + +- **EditThemePlugin**: Create, edit, save, and load custom themes for your DearPyGui applications +- **ChooseFontsPlugin**: Choose from system fonts and custom fonts with size and scale adjustments +- Save themes to `.ini` files for easy sharing and reuse +- Live preview of theme changes +- Integrates seamlessly with DearPyGui's viewport menu bar + +## Requirements + +- Python 3.9+ (Python 3.14 supported) +- DearPyGui 2.1.1 or higher + +## Installation + +1. Clone this repository or download the plugin files +2. Install dependencies: +```bash +pip install -r requirements.txt ``` - import dearpygui.dearpygui as dpg - from EditThemePlugin import EditThemePlugin - dpg.create_context() - dpg.create_viewport(title="some title", width=1000, height=1000) - with dpg.viewport_menu_bar(): - with dpg.menu(): - dpg.add_menu_item(label="Show Metrics", callback=lambda:dpg.show_tool(dpg.mvTool_Metrics)) + +## Usage + +### EditThemePlugin + +Add the theme editor to your DearPyGui application: + +```python +import dearpygui.dearpygui as dpg +from EditThemePlugin import EditThemePlugin + +dpg.create_context() +dpg.create_viewport(title="My App", width=1000, height=1000) + +with dpg.viewport_menu_bar(): + with dpg.menu(label="Tools"): + dpg.add_menu_item(label="Show Metrics", callback=lambda: dpg.show_tool(dpg.mvTool_Metrics)) EditThemePlugin() - dpg.setup_dearpygui() - dpg.show_viewport() - dpg.start_dearpygui() - dpg.destroy_context() - exit() + +dpg.setup_dearpygui() +dpg.show_viewport() +dpg.start_dearpygui() +dpg.destroy_context() +``` + +Theme files are stored locally in a folder called `themes/`. The default theme is `themes/default.ini`, which is automatically created on first run. + +### ChooseFontsPlugin + +Add the font selector to your DearPyGui application: + +```python +import dearpygui.dearpygui as dpg +from ChooseFontsPlugin import ChooseFontsPlugin +from EditThemePlugin import EditThemePlugin + +dpg.create_context() +dpg.create_viewport(title="My App", width=1000, height=1000) + +with dpg.viewport_menu_bar(): + myFonts = ChooseFontsPlugin() + myTheme = EditThemePlugin() + +dpg.setup_dearpygui() +dpg.show_viewport() +dpg.start_dearpygui() +dpg.destroy_context() ``` -Files are storeed locally, in a folder called "themes". Default theme is "themes/default.ini". The program will create the default on start up. + +The ChooseFontsPlugin integrates perfectly with EditThemePlugin. Place your custom fonts (`.ttf` or `.otf` files) in the `Fonts/` folder, and they will appear in the font selector. + +## Testing + +Run the test suite to verify the plugins work correctly: + +```bash +python3 -m unittest test_plugins -v +``` + +The tests verify: +- Plugin initialization and folder creation +- Configuration file handling +- Color parsing and theme management +- Font file discovery (case-insensitive .ttf/.otf detection) +- Error handling + +### Screenshots + +#### Theme Editor + ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/8b0adc9f-7b59-4bec-bc95-5a9150afb4ef) ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/aaf1287e-d092-48d3-94e0-3f9251e8da84) ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/a8cb2c2c-9c78-46ab-89a9-36aa3f62cc3c) -Recently added ChooseFontsPlugin.py -Have your users choose any font they want for readability +#### Font Selector + ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/6b18d676-5ae2-4db9-9610-1e78793a4f93) ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/61cff4d4-8ec4-4795-a418-565aaf6989dc) @@ -33,14 +105,23 @@ Have your users choose any font they want for readability ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/aba14839-7ecf-4047-843e-f07af0557e9f) ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/69068f50-3987-41c0-9996-d93aab159bf4) ![image](https://github.com/awcook97/DearPyGui_EditThemePlugin/assets/8891546/125441f1-f591-40ad-9880-8a7ae7e29acb) -To use: -``` - from ChooseFontsPlugin import ChooseFontsPlugin - from EditThemePlugin import EditThemePlugin - ... - with dpg.menu_bar(): - myFonts = ChooseFontsPlugin() - myTheme = EditThemePlugin() -``` -Integrates perfectly with EditThemePlugin -You can also set a variable to the plugins if something goes wrong, you'll be able to pause the script and see what the variables are at. + +## Tips + +- You can assign the plugins to variables for debugging purposes: + ```python + with dpg.viewport_menu_bar(): + myFonts = ChooseFontsPlugin() + myTheme = EditThemePlugin() + ``` + If something goes wrong, you can pause the script and inspect these variables. + +- Themes and fonts are saved locally, so your customizations persist across sessions. + +## License + +See [LICENSE](LICENSE) file for details. + +## Contributing + +Contributions are welcome! Feel free to open issues or submit pull requests. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f04722d --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +dearpygui>=2.1.1 \ No newline at end of file diff --git a/test_plugins.py b/test_plugins.py new file mode 100644 index 0000000..cab4f47 --- /dev/null +++ b/test_plugins.py @@ -0,0 +1,334 @@ +""" +Tests for EditThemePlugin and ChooseFontsPlugin + +These tests verify that the plugins can be instantiated and their basic +functionality works correctly without requiring a full GUI environment. +""" + +import unittest +import os +import sys +import tempfile +import shutil +from unittest.mock import Mock, patch, MagicMock +import configparser + +# Mock dearpygui before importing plugins +sys.modules['dearpygui'] = MagicMock() +sys.modules['dearpygui.dearpygui'] = MagicMock() + + +class TestEditThemePlugin(unittest.TestCase): + """Test cases for EditThemePlugin.""" + + def setUp(self): + """Set up test environment.""" + # Create temporary directories for testing + self.test_dir = tempfile.mkdtemp() + self.themes_dir = os.path.join(self.test_dir, 'themes') + os.makedirs(self.themes_dir, exist_ok=True) + + # Change to test directory + self.original_dir = os.getcwd() + os.chdir(self.test_dir) + + def tearDown(self): + """Clean up test environment.""" + os.chdir(self.original_dir) + if os.path.exists(self.test_dir): + shutil.rmtree(self.test_dir) + + @patch('dearpygui.dearpygui') + def test_plugin_initialization(self, mock_dpg): + """Test that EditThemePlugin can be initialized.""" + mock_dpg.add_theme = Mock() + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.add_button = Mock() + mock_dpg.add_file_dialog = Mock() + mock_dpg.add_file_extension = Mock() + mock_dpg.theme_component = Mock() + mock_dpg.mvAll = 0 + + from EditThemePlugin import EditThemePlugin + + plugin = EditThemePlugin() + + # Verify plugin was created + self.assertIsNotNone(plugin) + self.assertIsNotNone(plugin.confParser) + self.assertIsNotNone(plugin.theColors) + + # Verify theme folder was created + self.assertTrue(os.path.exists('themes')) + + # Verify default theme file was created + self.assertTrue(os.path.exists('themes/default.ini')) + + @patch('dearpygui.dearpygui') + def test_default_theme_creation(self, mock_dpg): + """Test that default theme file is properly created.""" + mock_dpg.add_theme = Mock() + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.add_button = Mock() + mock_dpg.add_file_dialog = Mock() + mock_dpg.add_file_extension = Mock() + mock_dpg.theme_component = Mock() + mock_dpg.mvAll = 0 + + from EditThemePlugin import EditThemePlugin + + plugin = EditThemePlugin() + + # Verify default theme file has content + self.assertTrue(os.path.exists('themes/default.ini')) + + # Parse the default theme file + config = configparser.ConfigParser() + config.read('themes/default.ini') + + # Verify Theme section exists + self.assertIn('Theme', config) + + # Verify some color entries exist + self.assertIn('mvthemecol_text', config['Theme']) + self.assertIn('mvthemecol_button', config['Theme']) + + def test_color_parsing(self): + """Test getThemeColor method parses colors correctly.""" + from EditThemePlugin import EditThemePlugin + + # Create a plugin instance with minimal mocking + with patch('dearpygui.dearpygui') as mock_dpg: + mock_dpg.add_theme = Mock() + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.add_button = Mock() + mock_dpg.add_file_dialog = Mock() + mock_dpg.add_file_extension = Mock() + mock_dpg.theme_component = Mock() + mock_dpg.mvAll = 0 + + plugin = EditThemePlugin() + + # Test color parsing + plugin.confParser['Theme']['test_color'] = "255.0, 128.0, 64.0, 255.0" + result = plugin.getThemeColor('test_color') + + self.assertEqual(result, (255, 128, 64, 255)) + + def test_color_parsing_error_handling(self): + """Test getThemeColor handles errors gracefully.""" + from EditThemePlugin import EditThemePlugin + + with patch('dearpygui.dearpygui') as mock_dpg: + mock_dpg.add_theme = Mock() + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.add_button = Mock() + mock_dpg.add_file_dialog = Mock() + mock_dpg.add_file_extension = Mock() + mock_dpg.theme_component = Mock() + mock_dpg.mvAll = 0 + + plugin = EditThemePlugin() + + # Test with invalid color + plugin.confParser['Theme']['invalid_color'] = "not a color" + result = plugin.getThemeColor('invalid_color') + + # Should return default white + self.assertEqual(result, (255, 255, 255, 255)) + + +class TestChooseFontsPlugin(unittest.TestCase): + """Test cases for ChooseFontsPlugin.""" + + def setUp(self): + """Set up test environment.""" + # Create temporary directories for testing + self.test_dir = tempfile.mkdtemp() + self.fonts_dir = os.path.join(self.test_dir, 'Fonts') + os.makedirs(self.fonts_dir, exist_ok=True) + + # Change to test directory + self.original_dir = os.getcwd() + os.chdir(self.test_dir) + + # Create test font files + with open(os.path.join(self.fonts_dir, 'test.ttf'), 'w') as f: + f.write('fake font data') + with open(os.path.join(self.fonts_dir, 'TEST.TTF'), 'w') as f: + f.write('fake font data uppercase') + with open(os.path.join(self.fonts_dir, 'test.otf'), 'w') as f: + f.write('fake otf font') + with open(os.path.join(self.fonts_dir, 'test.OTF'), 'w') as f: + f.write('fake OTF font uppercase') + + def tearDown(self): + """Clean up test environment.""" + os.chdir(self.original_dir) + if os.path.exists(self.test_dir): + shutil.rmtree(self.test_dir) + + @patch('dearpygui.dearpygui') + def test_plugin_initialization(self, mock_dpg): + """Test that ChooseFontsPlugin can be initialized.""" + mock_dpg.add_font_registry = Mock(return_value=1) + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.window = MagicMock() + mock_dpg.child_window = MagicMock() + mock_dpg.group = MagicMock() + mock_dpg.add_text = Mock() + mock_dpg.add_combo = Mock() + mock_dpg.add_input_int = Mock() + mock_dpg.add_slider_float = Mock() + mock_dpg.add_checkbox = Mock() + mock_dpg.add_button = Mock() + mock_dpg.get_viewport_width = Mock(return_value=1200) + mock_dpg.get_viewport_height = Mock(return_value=800) + + from ChooseFontsPlugin import ChooseFontsPlugin + + plugin = ChooseFontsPlugin() + + # Verify plugin was created + self.assertIsNotNone(plugin) + self.assertIsNotNone(plugin.fontDict) + + # Verify Fonts folder was created + self.assertTrue(os.path.exists('Fonts')) + + @patch('dearpygui.dearpygui') + def test_font_file_discovery_case_insensitive(self, mock_dpg): + """Test that font files are discovered case-insensitively.""" + mock_dpg.add_font_registry = Mock(return_value=1) + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.window = MagicMock() + mock_dpg.child_window = MagicMock() + mock_dpg.group = MagicMock() + mock_dpg.add_text = Mock() + mock_dpg.add_combo = Mock() + mock_dpg.add_input_int = Mock() + mock_dpg.add_slider_float = Mock() + mock_dpg.add_checkbox = Mock() + mock_dpg.add_button = Mock() + mock_dpg.get_viewport_width = Mock(return_value=1200) + mock_dpg.get_viewport_height = Mock(return_value=800) + mock_dpg.font = MagicMock() + mock_dpg.add_font_range_hint = Mock() + mock_dpg.add_font_range = Mock() + mock_dpg.mvFontRangeHint_Default = 0 + mock_dpg.mvFontRangeHint_Cyrillic = 1 + + from ChooseFontsPlugin import ChooseFontsPlugin + + plugin = ChooseFontsPlugin() + + # Check that font files were discovered (case-insensitive) + font_files = [k for k in plugin.fontDict.keys() if k.lower().endswith(('.ttf', '.otf'))] + + # Should find all 4 test font files + self.assertEqual(len(font_files), 4) + self.assertIn('test.ttf', font_files) + self.assertIn('TEST.TTF', font_files) + self.assertIn('test.otf', font_files) + self.assertIn('test.OTF', font_files) + + @patch('dearpygui.dearpygui') + def test_user_preferences_files_created(self, mock_dpg): + """Test that user preference files are created.""" + mock_dpg.add_font_registry = Mock(return_value=1) + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.window = MagicMock() + mock_dpg.child_window = MagicMock() + mock_dpg.group = MagicMock() + mock_dpg.add_text = Mock() + mock_dpg.add_combo = Mock() + mock_dpg.add_input_int = Mock() + mock_dpg.add_slider_float = Mock() + mock_dpg.add_checkbox = Mock() + mock_dpg.add_button = Mock() + mock_dpg.get_viewport_width = Mock(return_value=1200) + mock_dpg.get_viewport_height = Mock(return_value=800) + + from ChooseFontsPlugin import ChooseFontsPlugin + + plugin = ChooseFontsPlugin() + + # Verify preference files were created + self.assertTrue(os.path.exists('Fonts/USERFONT')) + self.assertTrue(os.path.exists('Fonts/USERSIZE')) + self.assertTrue(os.path.exists('Fonts/USERSCALE')) + + +class TestIntegration(unittest.TestCase): + """Integration tests for both plugins.""" + + def setUp(self): + """Set up test environment.""" + self.test_dir = tempfile.mkdtemp() + self.original_dir = os.getcwd() + os.chdir(self.test_dir) + + def tearDown(self): + """Clean up test environment.""" + os.chdir(self.original_dir) + if os.path.exists(self.test_dir): + shutil.rmtree(self.test_dir) + + @patch('dearpygui.dearpygui') + def test_both_plugins_can_coexist(self, mock_dpg): + """Test that both plugins can be instantiated together.""" + # Mock all DearPyGui functions + mock_dpg.add_theme = Mock() + mock_dpg.add_menu = Mock(return_value=1) + mock_dpg.add_menu_item = Mock() + mock_dpg.add_window = Mock() + mock_dpg.add_button = Mock() + mock_dpg.add_file_dialog = Mock() + mock_dpg.add_file_extension = Mock() + mock_dpg.theme_component = MagicMock() + mock_dpg.mvAll = 0 + mock_dpg.add_font_registry = Mock(return_value=1) + mock_dpg.window = MagicMock() + mock_dpg.child_window = MagicMock() + mock_dpg.group = MagicMock() + mock_dpg.add_text = Mock() + mock_dpg.add_combo = Mock() + mock_dpg.add_input_int = Mock() + mock_dpg.add_slider_float = Mock() + mock_dpg.add_checkbox = Mock() + mock_dpg.get_viewport_width = Mock(return_value=1200) + mock_dpg.get_viewport_height = Mock(return_value=800) + + from EditThemePlugin import EditThemePlugin + from ChooseFontsPlugin import ChooseFontsPlugin + + # Create both plugins + theme_plugin = EditThemePlugin() + font_plugin = ChooseFontsPlugin() + + # Verify both were created + self.assertIsNotNone(theme_plugin) + self.assertIsNotNone(font_plugin) + + # Verify separate directories exist + self.assertTrue(os.path.exists('themes')) + self.assertTrue(os.path.exists('Fonts')) + + +if __name__ == '__main__': + # Run tests + unittest.main(verbosity=2)