diff --git a/.gitignore b/.gitignore index 441043a..863f3b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,133 +1,133 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -cfg.json -lastfile.txt +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +cfg.json +lastfile.txt .vscode \ No newline at end of file diff --git a/LICENSE b/LICENSE index 23439fd..a42cabb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2022 not-nef - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2023 not-nef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/assets/check_dark.png b/assets/check_dark.png new file mode 100644 index 0000000..b8576f5 Binary files /dev/null and b/assets/check_dark.png differ diff --git a/assets/check_light.png b/assets/check_light.png new file mode 100644 index 0000000..e249cdf Binary files /dev/null and b/assets/check_light.png differ diff --git a/assets/close_dark.png b/assets/close_dark.png index bdb4038..5296a28 100644 Binary files a/assets/close_dark.png and b/assets/close_dark.png differ diff --git a/assets/close_light.png b/assets/close_light.png index 5296a28..bdb4038 100644 Binary files a/assets/close_light.png and b/assets/close_light.png differ diff --git a/assets/filetypes/css_dark.png b/assets/filetypes/css_dark.png new file mode 100644 index 0000000..11bddcd Binary files /dev/null and b/assets/filetypes/css_dark.png differ diff --git a/assets/filetypes/css_light.png b/assets/filetypes/css_light.png new file mode 100644 index 0000000..f49d29e Binary files /dev/null and b/assets/filetypes/css_light.png differ diff --git a/assets/filetypes/html_dark.png b/assets/filetypes/html_dark.png new file mode 100644 index 0000000..19db55b Binary files /dev/null and b/assets/filetypes/html_dark.png differ diff --git a/assets/filetypes/html_light.png b/assets/filetypes/html_light.png new file mode 100644 index 0000000..4876a4a Binary files /dev/null and b/assets/filetypes/html_light.png differ diff --git a/assets/filetypes/js_dark.png b/assets/filetypes/js_dark.png new file mode 100644 index 0000000..0b292fb Binary files /dev/null and b/assets/filetypes/js_dark.png differ diff --git a/assets/filetypes/js_light.png b/assets/filetypes/js_light.png new file mode 100644 index 0000000..2dcbfc1 Binary files /dev/null and b/assets/filetypes/js_light.png differ diff --git a/assets/filetypes/md_dark.png b/assets/filetypes/md_dark.png new file mode 100644 index 0000000..8a4d1cb Binary files /dev/null and b/assets/filetypes/md_dark.png differ diff --git a/assets/filetypes/md_light.png b/assets/filetypes/md_light.png new file mode 100644 index 0000000..2b892b0 Binary files /dev/null and b/assets/filetypes/md_light.png differ diff --git a/assets/filetypes/other_dark.png b/assets/filetypes/other_dark.png new file mode 100644 index 0000000..8bd4cb7 Binary files /dev/null and b/assets/filetypes/other_dark.png differ diff --git a/assets/filetypes/other_light.png b/assets/filetypes/other_light.png new file mode 100644 index 0000000..dbe43ab Binary files /dev/null and b/assets/filetypes/other_light.png differ diff --git a/assets/filetypes/txt_dark.png b/assets/filetypes/txt_dark.png new file mode 100644 index 0000000..574d756 Binary files /dev/null and b/assets/filetypes/txt_dark.png differ diff --git a/assets/filetypes/txt_light.png b/assets/filetypes/txt_light.png new file mode 100644 index 0000000..d84b9cb Binary files /dev/null and b/assets/filetypes/txt_light.png differ diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index 3f8e7d4..9daa0b6 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -1,128 +1,128 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -GitHub Issues. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +GitHub Issues. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 6af5b20..7f2bf72 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,24 +1,24 @@ -## Contributing to txt2 - -Start off by creating a fork of the repository on github. -Then clone this fork with git: - -```git clone https://github.com/your_username/txt2``` - -And checkout to a new branch: - -```git checkout -b my_new_feature``` - -Now open the generated txt2 folder with a code editor of your choice and make your edits. -Then stage your changes: - -```git add .``` - -And commit and push: - -``` -git commit -m "My New Feature" -git push -``` - -Now create a pull request on the txt2 repo. +## Contributing to txt2 + +Start off by creating a fork of the repository on github. +Then clone this fork with git: + +```git clone https://github.com/your_username/txt2``` + +And checkout to a new branch: + +```git checkout -b my_new_feature``` + +Now open the generated txt2 folder with a code editor of your choice and make your edits. +Then stage your changes: + +```git add .``` + +And commit and push: + +``` +git commit -m "My New Feature" +git push +``` + +Now create a pull request on the txt2 repo. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 6365979..c0c1df3 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -1,17 +1,17 @@ -# v0.2 - -## File -- Edit -- Save / Save As -- Open -- Create New -- Change Extension - -## Hotkeys (DISABLED) -- `ctrl+s` to save -- `ctrl+o` to open - -## UI -- Window is made slightly smaller than, and placed in the middle of, the screen at startup -- Display save directory of opened file at bottom of screen -- Menu bar with dropdown menus +# v0.2 + +## File +- Edit +- Save / Save As +- Open +- Create New +- Change Extension + +## Hotkeys (DISABLED) +- `ctrl+s` to save +- `ctrl+o` to open + +## UI +- Window is made slightly smaller than, and placed in the middle of, the screen at startup +- Display save directory of opened file at bottom of screen +- Menu bar with dropdown menus diff --git a/docs/README.md b/docs/README.md index 61fdbf5..9cbe5a2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,13 +1,13 @@ -
- -# Futura Notes - - Releases - -

- -A text editor in python! - -Modern and native look thanks to rdbendes [Sun Valley ttk theme](https://github.com/rdbende/Sun-Valley-ttk-theme) - -You can find more information in the [wiki](https://github.com/not-nef/onyx/wiki) +
+ +# Futura Notes + + Releases + +

+ +A text editor in python! + +Modern and native look thanks to rdbendes [Sun Valley ttk theme](https://github.com/rdbende/Sun-Valley-ttk-theme) + +You can find more information in the [wiki](https://github.com/not-nef/onyx/wiki) diff --git a/requirements.txt b/requirements.txt index d07c363..f40cc47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,13 @@ -sv-ttk >= 2.4 -ntkutils -darkdetect -pywin32; sys_platform == 'win32' -chlorophyll -markdown -tkinterweb -tkinterdnd2 -tklinenums >= 1.5 -requests; sys_platform != 'linux' -pygments -pyperclip +sv-ttk >= 2.4 +ntkutils +darkdetect +pywin32; sys_platform == 'win32' +chlorophyll +markdown +tkinterweb +tkinterdnd2 +tklinenums >= 1.5 +requests; sys_platform != 'linux' +pygments +pyperclip +pillow == 10.0.0 # tkinterweb requires version 10.0.0 diff --git a/src/config.py b/src/config.py deleted file mode 100644 index 04208fd..0000000 --- a/src/config.py +++ /dev/null @@ -1,51 +0,0 @@ -import json - -import ntkutils.cfgtools as cfgtools - -from generatesize import system - -default_win = { - "theme": "Dark", - "font": "Helvetica", - "font-size": 11, - "mica": False, - "hkey-open": "Control-o", - "hkey-save": "Control-s", - "linenumbers": True, - "syntax-highlighting": False, - "onclose": "Ask", -} - -default_mac = { - "theme": "Dark", - "font": "Helvetica", - "font-size": 11, - "mica": False, - "hkey-open": "Command-o", - "hkey-save": "Command-s", - "linenumbers": True, - "syntax-highlighting": False, -} - -# Update Config if settings are missing -try: - config_file = open("cfg.json") - cfg = json.load(config_file) - config_file.close() - - if len(cfg) != len(default_win): - temp_cfg = default_win.copy() - for i in cfg: - temp_cfg.pop(i) - for i in temp_cfg: - cfg[i] = temp_cfg[i] - cfgtools.SaveCFG(cfg) -except FileNotFoundError: - pass - - -def get(): - if system != "Darwin": - return cfgtools.init(default_win) - else: - return cfgtools.init(default_mac) diff --git a/src/dark.toml b/src/dark.toml new file mode 100644 index 0000000..6f4800a --- /dev/null +++ b/src/dark.toml @@ -0,0 +1,79 @@ +[editor] +bg = "#1c1c1c" +fg = "#ffffff" +select_bg = "#1b2733" +inactive_select_bg = "#1b2733" +caret = "#b3b1ad" +caret_width = 1 +border_width = 0 +focus_border_width = 0 + +[general] +comment = "#626a73" +error = "#ff3333" +escape = "#b3b1ad" +keyword = "#ff7700" +name = "#ff8f40" +string = "#95e6cb" +punctuation = "#b3b1ad" + +[keyword] +constant = "#ff7700" +declaration = "#ff7700" +namespace = "#ff7700" +pseudo = "#ff7700" +reserved = "#ff7700" +type = "#ff7700" + +[name] +attr = "#ff8f40" +builtin = "#e6b450" +builtin_pseudo = "#e6b450" +class = "#ff8f40" +class_variable = "#ff8f40" +constant = "#ffee99" +decorator = "#e6b673" +entity = "#ff8f40" +exception = "#ff8f40" +function = "#ffb454" +global_variable = "#ff8f40" +instance_variable = "#ff8f40" +label = "#ff8f40" +magic_function = "#ff8f40" +magic_variable = "#ff8f40" +namespace = "#b3b1ad" +tag = "#ff8f40" +variable = "#ff8f40" + +[operator] +symbol = "#f29668" +word = "#f29668" + +[string] +affix = "#c2d94c" +char = "#95e6cb" +delimeter = "#c2d94c" +doc = "#c2d94c" +double = "#c2d94c" +escape = "#c2d94c" +heredoc = "#c2d94c" +interpol = "#c2d94c" +regex = "#95e6cb" +single = "#c2d94c" +symbol = "#c2d94c" + +[number] +binary = "#e6b450" +float = "#e6b450" +hex = "#e6b450" +integer = "#e6b450" +long = "#e6b450" +octal = "#e6b450" + +[comment] +hashbang = "#626a73" +multiline = "#626a73" +preproc = "#ff7700" +preprocfile = "#c2d94c" +single = "#626a73" +special = "#626a73" \ No newline at end of file diff --git a/src/dialogs.py b/src/dialogs.py new file mode 100644 index 0000000..5ff3266 --- /dev/null +++ b/src/dialogs.py @@ -0,0 +1,190 @@ +# Property of rdbende licensed under the MIT License +# https://github.com/rdbende/Sun-Valley-messageboxes/blob/main/dialogs.py + +import sys +import tkinter as tk +from tkinter import ttk +from functools import partial + + +def popup(parent, title, details, icon, *, buttons): + dialog = tk.Toplevel() + + result = None + + big_frame = ttk.Frame(dialog) + big_frame.pack(fill="both", expand=True) + big_frame.columnconfigure(0, weight=1) + big_frame.rowconfigure(0, weight=1) + + info_frame = ttk.Frame(big_frame, padding=(10, 12), style="Dialog_info.TFrame") + info_frame.grid(row=0, column=0, sticky="nsew") + info_frame.columnconfigure(1, weight=1) + info_frame.rowconfigure(1, weight=1) + + try: + color = big_frame.tk.call("set", "themeColors::dialogInfoBg") + except tk.TclError: + color = big_frame.tk.call("ttk::style", "lookup", "TFrame", "-background") + + icon_label = ttk.Label(info_frame, image=icon, anchor="nw", background=color) + if icon is not None: + icon_label.grid( + row=0, column=0, sticky="nsew", padx=(12, 0), pady=10, rowspan=2 + ) + + title_label = ttk.Label( + info_frame, text=title, anchor="nw", font=("", 14, "bold"), background=color + ) + title_label.grid(row=0, column=1, sticky="nsew", padx=(12, 17), pady=(10, 8)) + + detail_label = ttk.Label(info_frame, text=details, anchor="nw", background=color) + detail_label.grid(row=1, column=1, sticky="nsew", padx=(12, 17), pady=(5, 10)) + + button_frame = ttk.Frame( + big_frame, padding=(22, 22, 12, 22), style="Dialog_buttons.TFrame" + ) + button_frame.grid(row=2, column=0, sticky="nsew") + + def on_button(value): + nonlocal result + result = value + dialog.destroy() + + for index, button_value in enumerate(buttons): + style = None + state = None + default = False + sticky = "nes" if len(buttons) == 1 else "nsew" + + if len(button_value) > 2: + if button_value[2] == "accent": + style = "Accent.TButton" + default = True + elif button_value[2] == "disabled": + state = "disabled" + elif button_value[2] == "default": + default = True + + button = ttk.Button( + button_frame, + text=button_value[0], + width=18, + command=partial(on_button, button_value[1]), + style=style, + state=state, + ) + if default: + button.bind("", button["command"]) + button.focus() + + button.grid(row=0, column=index, sticky=sticky, padx=(0, 10)) + + button_frame.columnconfigure(index, weight=1) + + if sys.platform == "win32": + transparent_color = big_frame.tk.call("ttk::style", "lookup", "TFrame", "-background") + dialog.wm_attributes("-transparentcolor", transparent_color) + + # dialog.overrideredirect(True) + dialog.update_idletasks() + + dialog_width = dialog.winfo_width() + dialog_height = dialog.winfo_height() + + if parent is None: + parent_width = dialog.winfo_screenwidth() + parent_height = dialog.winfo_screenheight() + parent_x = 0 + parent_y = 0 + else: + parent_width = parent.winfo_width() + parent_height = parent.winfo_height() + parent_x = parent.winfo_x() + parent_y = parent.winfo_y() + + x_coord = int(parent_width / 2 + parent_x - dialog_width / 2) + y_coord = int(parent_height / 2 + parent_y - dialog_height / 2) + + dialog.geometry("+{}+{}".format(x_coord, y_coord)) + dialog.minsize(320, dialog_height) + + dialog.transient(parent) + dialog.wm_attributes("-type", "dialog") + dialog.grab_set() + + dialog.wait_window() + return result + + +def show_message(title="Title", details="Description", *, parent=None, icon=None): + return popup( + parent, + title, + details, + icon, + buttons=[("Ok", None, "default")], + ) + + +def ask_ok_cancel(title="Title", details="Description", *, parent=None, icon=None): + return popup( + parent, + title, + details, + icon, + buttons=[("Ok", True, "accent"), ("Cancel", None)], + ) + + +def ask_yes_no(title="Title", details="Description", *, parent=None, icon=None): + return popup( + parent, + title, + details, + icon, + buttons=[("Yes", True, "accent"), ("No", False)], + ) + + +def ask_yes_no_cancel(title="Title", details="Description", *, parent=None, icon=None): + return popup( + parent, + title, + details, + icon, + buttons=[("Yes", True, "accent"), ("No", False), ("Cancel", None)], + ) + + +def ask_retry_cancel(title="Title", details="Description", *, parent=None, icon=None): + return popup( + parent, + title, + details, + icon, + buttons=[("Retry", True, "accent"), ("Cancel", None)], + ) + + +def ask_allow_block(title="Title", details="Description", *, parent=None, icon=None): + return popup( + parent, + title, + details, + icon, + buttons=[("Allow", True, "accent"), ("Block", False)], + ) + + +if __name__ == "__main__": + window = tk.Tk() + + window.tk.call("source", "sun-valley.tcl") + window.tk.call("set_theme", "dark") + + window.geometry("600x600") + + show_message("No WiFi connection", "Check your connection and try again.") + + window.mainloop() \ No newline at end of file diff --git a/src/editor.py b/src/editor.py index 0466a05..2e8a6ff 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,212 +1,148 @@ -import tkinter -from pathlib import Path -from tkinter import ttk -from tkinter.font import Font - -import chlorophyll -import darkdetect -import ntkutils -import pygments -import pyperclip -from tkinterdnd2 import * -from tklinenums import TkLineNumbers - -import mdpreview as md -import pages.about as about -import pages.wanttosave as w -import settings.UI as settingsui -import tabmanager -import vars as v -from settings.images import setimages - - -def build(theme, root, ver): - closeimg = tkinter.PhotoImage(file=Path(theme["closeimg"])) - - def closepreview(): - md.close() - textwidget.bind("", refreshtitle) - if v.cfg["linenumbers"]: - textwidget.bind( - f"", lambda event: root.after_idle(linenums.redraw), add=True - ) - - notebook = ttk.Notebook(root) - notebook.pack(fill="both", expand=True) - - footer = tkinter.Frame(root, width=root.winfo_width(), height=25) - footer.update_idletasks() - footer.pack(side="bottom", fill="x") - footer.pack_propagate(False) - - scrollbar = ttk.Scrollbar(root) - scrollbar.pack(side="right", fill="y") - - if v.cfg["syntax-highlighting"]: - textwidget = chlorophyll.CodeView( - root, - height=800, - bg=theme["primary"], - lexer=pygments.lexers.TextLexer, - font=(v.cfg["font"], int(v.cfg["font-size"])), - ) - textwidget._set_color_scheme(theme["color_scheme"]) - textwidget.pack(side="right", fill="both", expand=True) - textwidget._hs.grid_remove() - textwidget._vs.grid_remove() - else: - textwidget = tkinter.Text( - root, - width=100, - borderwidth=0, - height=root.winfo_height() - 125, - font=(v.cfg["font"], int(v.cfg["font-size"])), - ) - textwidget.pack(side="right", fill="both", expand=True) - - textwidget.update() - - scrollbar.configure(command=textwidget.yview) - textwidget["yscrollcommand"] = scrollbar.set - - if v.cfg["linenumbers"]: - style = ttk.Style() - style.configure( - "TLineNumbers", - background=theme["primary"], - foreground=theme["opposite_secondary"], - ) - - font = Font( - family="Courier New bold", size=v.cfg["font-size"], name="TkLineNumsFont" - ) - - linenums = TkLineNumbers(root, textwidget, font, "right") - linenums.pack(side="left", fill="y") - linenums.configure(borderwidth=0) - linenums.reload(font) - - textwidget.bind( - "", lambda event: root.after_idle(linenums.redraw), add=True - ) - textwidget.bind( - f"", lambda event: root.after_idle(linenums.redraw), add=True - ) - textwidget.bind( - f"", lambda event: root.after_idle(linenums.redraw), add=True - ) - - def onscroll(first, last): - scrollbar.set(first, last) - linenums.redraw() - - textwidget["yscrollcommand"] = onscroll - - textwidget.linenums = linenums - - filedir = tkinter.Label(footer, text="unsaved") - filedir.pack(side="left") - - menubar = tkinter.Menu(root) - root.config(menu=menubar) - - filemenu = tkinter.Menu(menubar, tearoff=False, bg="white") - settingsmenu = tkinter.Menu(menubar, tearoff=False, bg="white") - - menubar.add_cascade(label="File", menu=filemenu) - menubar.add_cascade(label="Settings", menu=settingsmenu) - - filemenu.add_command(label="Save ({})".format(v.cfg["hkey-save"]), command=tabmanager.save, foreground="black" ) - filemenu.add_command(label="Save As", command=lambda: tabmanager.save(saveas=True), foreground="black") - filemenu.add_command(label="Open ({})".format(v.cfg["hkey-open"]), command=tabmanager.openfile, foreground="black") - filemenu.add_command(label="New", command=tabmanager.new, foreground="black") - filemenu.add_separator() - filemenu.add_command(label="Change file extension", command=tabmanager.changetype, foreground="black") - filemenu.add_separator() - filemenu.add_command(label="Preview Markdown", command=md.build, foreground="black") - filemenu.add_command(label="Close Preview", command=closepreview, foreground="black") - - settingsmenu.add_command(label="Open Settings", command=settingsui.build, foreground="black") - settingsmenu.add_command(label="About", command=about.build, foreground="black") - - if v.cfg["mica"]: - if v.cfg["theme"] == "Dark" or (v.cfg["theme"] == "System" and darkdetect.isDark()): - notebook.configure(bg="#1c1c1c") - ntkutils.blur_window_background(root, dark=True) - textwidget.text.configure(bg="#1b1c1b") - try: - textwidget.numberLines.configure(bg="#1b1c1b") - except: - pass - else: - ntkutils.blur_window_background(root) - textwidget.text.configure(bg="#fafbfa") - - def refreshtitle(e): - if not root.wm_title().endswith("*"): - root.title(root.wm_title() + "*") - tabmanager.tabs[v.tabselected][3] = "*" - - textwidget.bind("", refreshtitle) - - root.event_add("<>", "<{}>".format(v.cfg["hkey-open"])) - root.event_add("<>", "<{}>".format(v.cfg["hkey-save"])) - - root.bind("<>", tabmanager.openfile) - root.bind("<>", tabmanager.save) - - def filedrop(event): - tabmanager.openfile(path=event.data) - - root.drop_target_register(DND_FILES) - root.dnd_bind("<>", filedrop) - - def cut(): - pyperclip.copy(textwidget.selection_get()) - textwidget.delete("sel.first", "sel.last") - - def copy(): - pyperclip.copy(textwidget.selection_get()) - - def paste(): - textwidget.insert("insert", pyperclip.paste()) - - def popup(event): - try: - context.tk_popup(event.x_root, event.y_root, 0) - finally: - context.grab_release() - - context = tkinter.Menu(root, tearoff=False, bg="white") - context.add_command(label="Cut", command=cut, foreground="black") - context.add_command(label="Copy", command=copy, foreground="black") - context.add_command(label="Paste", command=paste, foreground="black") - root.bind("", popup) - - def on_closing(): - if v.cfg["onclose"] == "Do Nothing": - root.destroy() - elif v.cfg["onclose"] == "Save": - tabmanager.save() - root.destroy() - else: - w.build() - - - root.protocol("WM_DELETE_WINDOW", on_closing) - - # Set global variables - v.root = root - v.textwidget = textwidget - v.filedir = filedir - v.tabbar = notebook - v.footer = footer - v.closeimg = closeimg - v.theme = theme - v.ver = ver - - setimages() - - notebook.add(tkinter.Frame(), text=tabmanager.tabs[0][0], image=closeimg, compound="right") - # Bind Left mouse button to write content of selected tab into the text widget - notebook.bind("", tabmanager.click, add="+") +from markdown import markdown +from os.path import basename, isfile +from tkfilebrowser import askopenfilename, asksaveasfilename +from tkinter import Frame, Label, PhotoImage +from tkinter.ttk import Button, Notebook, Style +from tkinterweb import HtmlFrame +from toml import load + +from chlorophyll import CodeView +from pygments.lexers import TextLexer, get_lexer_for_filename +from pygments.util import ClassNotFound + + +class Manager(Notebook): + def __init__(self, theme, *args): + super().__init__(*args) + + self.theme = theme + + self.closeimg = PhotoImage(file="assets/close_{}.png".format(theme)) + + # Remove dotted line :O + self.style = Style() + self.style.configure("TNotebook.Tab", focuscolor=self.style.configure(".")["background"]) + + self.home = Frame(self) + self.title = Label(self.home, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20) + self.btncreatenew = Button(self.home, text="Create New File", command=self.newtab).pack(anchor="nw", padx=20) + self.btnopen = Button(self.home, text="Open File", command=self.openfile).pack(anchor="nw", padx=20, pady=20) + self.add(self.home, text="Home", image=self.closeimg, compound="right") + + self.bind("", self.on_click, add=True) + + def newtab(self, file=None): + self.add(Editor(file, self.theme), text="Untitled" if file==None else basename(file.name), image=self.closeimg, compound="right") + self.select(self.tabs()[-1]) # Select newly opened tab + + def openfile(self): + self.file = open(askopenfilename(), "r") + self.newtab(self.file) + self.file.close() + + def save(self): + self.editor = self.getcurrentchild() + self.filetosave = self.editor.filedir.cget("text") + if isfile(self.filetosave): + self.file2 = open(self.filetosave, "w") + self.file2.write(self.editor.text.get("1.0", "end")) + self.file2.close() + else: + self.saveas() + + def saveas(self): + self.editor = self.getcurrentchild() + self.file3 = open(asksaveasfilename(), "w") + if self.file3 != None: + self.file3.write(self.editor.text.get("1.0", "end")) + self.editor.filedir.configure(text=self.file3.name) + self.tab(self.select(), text=basename(self.file3.name)) + self.file3.close() + + def getcurrentchild(self): + return self.nametowidget(self.select()) + + + # The next two functions are heavily inspired by Akuli: + # https://github.com/Akuli/porcupine/blob/main/porcupine/plugins/tab_closing.py + def closetab(self, event): + self.before = self.index(f"@{event.x},{event.y}") + self.after = self.before + 1 + self.forget(self.tabs()[self.before:self.after][0]) + if len(self.tabs()) == 0: self.master.destroy() + + def on_click(self, event) -> None: + self.update_idletasks() + if event.widget.identify(event.x, event.y) == "label": + # find the right edge of the top label (including close button) + right = event.x + while event.widget.identify(right, event.y) == "label": + right += 1 + + # hopefully the image is on the right edge of the label and there's no padding :O + if event.x >= right - self.closeimg.width(): + self.closetab(event) + + def openpreview(self): + self.nametowidget(self.select()).openpreview() + + +class Editor(Frame): + def __init__(self, file, theme, *args): + super().__init__(*args) + + self.ispreviewed = False + + self.footer = Frame(self, width=self.winfo_width(), height=25) + self.footer.pack(side="bottom", fill="x") + self.footer.pack_propagate(False) + + self.filedir = Label(self.footer, text="unsaved") + self.filedir.pack(side="left") + + self.text = CodeView(self) + self.text.pack(side="left", fill="both", expand=True) + self.text._hs.grid_remove() + + self.color_scheme = load("src/{}.toml".format(theme)) + self.text._set_color_scheme(self.color_scheme) + + self.text._line_numbers.configure(borderwidth=0) + + if file != None: + self.text.insert("1.0", file.read()) + self.filedir.configure(text=file.name) + self.text._set_lexer(self.get_lexer(file)) + file.close() + + def get_lexer(self, file): + try: + lexer = get_lexer_for_filename(file.name) + except ClassNotFound: + lexer = TextLexer + + return lexer + + def openpreview(self): + if not self.ispreviewed: + self.ispreviewed = True + self.update() + self.preview = HtmlFrame(self, messages_enabled = False, width = int(self.winfo_width() / 2), vertical_scrollbar=False) + self.preview.pack_propagate = False + self.preview.pack(side="right", fill = "both", expand=True) + self.preview.on_link_click(self.updatepreview) + + self.text.bind("", self.updatepreview, add=True) + self.text.bind("", self.updatepreview, add=True) + self.updatepreview() + else: + self.ispreviewed = False + self.preview.destroy() + + def updatepreview(self, *args): + if self.ispreviewed: + self.after_idle(lambda: self.preview.load_html(markdown(self.text.get("1.0", "end")))) + + + + diff --git a/src/generatesize.py b/src/generatesize.py deleted file mode 100644 index 5136cd9..0000000 --- a/src/generatesize.py +++ /dev/null @@ -1,21 +0,0 @@ -import platform - -system = platform.system() -if system == "Windows": - from win32api import GetMonitorInfo, MonitorFromPoint - - def get(): - monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0))) - work_area = monitor_info.get("Work") - return "{}x{}".format(work_area[2] - 40, work_area[3] - 80) - -else: - import tkinter - - root = tkinter.Tk() - root.withdraw() - WIDTH, HEIGHT = root.winfo_screenwidth(), root.winfo_screenheight() - root.destroy() - - def get(): - return "{}x{}".format(WIDTH, HEIGHT - 100) diff --git a/src/light.toml b/src/light.toml new file mode 100644 index 0000000..2df0ac8 --- /dev/null +++ b/src/light.toml @@ -0,0 +1,79 @@ +[editor] +bg = "#fafafa" +fg = "#000000" +select_bg = "#1b2733" +inactive_select_bg = "#1b2733" +caret = "#b3b1ad" +caret_width = 1 +border_width = 0 +focus_border_width = 0 + +[general] +comment = "#626a73" +error = "#ff3333" +escape = "#b3b1ad" +keyword = "#ff7700" +name = "#ff8f40" +string = "#95e6cb" +punctuation = "#b3b1ad" + +[keyword] +constant = "#ff7700" +declaration = "#ff7700" +namespace = "#ff7700" +pseudo = "#ff7700" +reserved = "#ff7700" +type = "#ff7700" + +[name] +attr = "#ff8f40" +builtin = "#e6b450" +builtin_pseudo = "#e6b450" +class = "#ff8f40" +class_variable = "#ff8f40" +constant = "#ffee99" +decorator = "#e6b673" +entity = "#ff8f40" +exception = "#ff8f40" +function = "#ffb454" +global_variable = "#ff8f40" +instance_variable = "#ff8f40" +label = "#ff8f40" +magic_function = "#ff8f40" +magic_variable = "#ff8f40" +namespace = "#b3b1ad" +tag = "#ff8f40" +variable = "#ff8f40" + +[operator] +symbol = "#f29668" +word = "#f29668" + +[string] +affix = "#c2d94c" +char = "#95e6cb" +delimeter = "#c2d94c" +doc = "#c2d94c" +double = "#c2d94c" +escape = "#c2d94c" +heredoc = "#c2d94c" +interpol = "#c2d94c" +regex = "#95e6cb" +single = "#c2d94c" +symbol = "#c2d94c" + +[number] +binary = "#e6b450" +float = "#e6b450" +hex = "#e6b450" +integer = "#e6b450" +long = "#e6b450" +octal = "#e6b450" + +[comment] +hashbang = "#626a73" +multiline = "#626a73" +preproc = "#ff7700" +preprocfile = "#c2d94c" +single = "#626a73" +special = "#626a73" \ No newline at end of file diff --git a/src/main.py b/src/main.py index 068651a..6b7c554 100644 --- a/src/main.py +++ b/src/main.py @@ -1,83 +1,138 @@ -ver = "0.9 beta" - -import os -import tkinter -from tkinter import ttk - -import ntkutils -import sv_ttk -from tkinterdnd2 import * - -import config -import editor -import generatesize as size -import settings.UI as settings -import tabmanager -import utils as u -import vars as v -from themes import dark, light - -v.cfg = config.get() - -if u.dark(): - theme = dark.get() -else: - theme = light.get() - -root = TkinterDnD.Tk() -root.geometry("200x350") -root.withdraw() -ntkutils.windowsetup(root, title="Futura Notes", resizeable=False) -sv_ttk.set_theme(v.cfg["theme"].lower()) -root.update_idletasks() -ntkutils.placeappincenter(root) -root.update_idletasks() - - -def preparewindow(): - root.title("Futura Notes - Untitled *") - ntkutils.clearwin(root) - root.geometry(size.get()) - root.update() - ntkutils.placeappincenter(root) - root.resizable(True, True) - editor.build(theme, root, ver) - - -def openfile(path): - preparewindow() - tabmanager.openfile(path=path) - -def settingss(): - preparewindow() - settings.build() - - -title = tkinter.Label(root, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20) -btncreatenew = ttk.Button(root, text="Create New File", command=preparewindow).pack(anchor="nw", padx=20) -btnopenfile = ttk.Button(root, text="Open File", command=lambda: openfile(path="")).pack(anchor="nw", pady=10, padx=20) -btnopendir = ttk.Button(root, text="Open Directory", state="disabled").pack(anchor="nw", padx=20) -btnopenlast = ttk.Button(root, text="Open last file", command=lambda: openfile(path=content)) -btnopenlast.pack(anchor="nw", padx=20, pady=20) - -if os.path.isfile("lastfile.txt"): - file = open("lastfile.txt", "r") - content = file.read() - file.close() - - if not os.path.isfile(content): - btnopenlast.configure(state="disabled") -else: - btnopenlast.configure(state="disabled") - -root.update_idletasks() -root.deiconify() -root.mainloop() - -# Save path of last opened file -content = tabmanager.tabs[v.tabselected][2] - -if content != "unsaved": - file = open("lastfile.txt", "w+") - file.write(content) - file.close() +from os import rename +from os.path import isfile +from tkinter import Menu, PhotoImage, Toplevel, Label, Frame +from tkinter.ttk import Entry, Button +from tkinterdnd2 import Tk, DND_FILES + +from sv_ttk import set_theme + +from dialogs import show_message +from editor import Manager +from platform import system + +BLOCKEDCHARS = "\\/:*?\"<>|" + +if system() == "Linux": LINUX = True +else: LINUX = False + +theme = "dark" +newfile = "" + +class App(Tk): + def __init__(self): + super().__init__() + + self.title("Futura Notes") + set_theme(theme) + + self.checkimg = PhotoImage(file="assets/check_light.png") + + self.h = self.winfo_screenheight() - 200 + self.w = self.winfo_screenwidth() - 100 + self.x = int((self.winfo_screenwidth() - self.w) / 2) + self.y = int((self.winfo_screenheight() - self.h - 75) / 2) + + self.geometry("{}x{}+{}+{}".format(self.w, self.h, self.x, self.y)) + + self.manager = Manager(theme, self) + self.manager.pack(fill="both", expand=True) + self.menubar = Menu(self, tearoff=False) + self.config(menu=self.menubar) + + self.filemenu = Menu(self.menubar, tearoff=False) + + self.menubar.add_cascade(label="File", menu=self.filemenu) + + self.filemenu.add_command(label="New", command=self.manager.newtab, background="white", foreground="black") + self.filemenu.add_command(label="Open", command=self.manager.openfile, background="white", foreground="black") + self.filemenu.add_command(label="Save", command=self.manager.save, background="white", foreground="black") + self.filemenu.add_command(label="Save As", command=self.manager.saveas, background="white", foreground="black") + self.filemenu.add_separator(background="white") + self.filemenu.add_command(label="Preview", command=self.openpreview, background="white", foreground="black", compound="right") + self.filemenu.add_separator(background="white") + self.filemenu.add_command(label="Properties", command=self.openproperties, background="white", foreground="black") + + self.drop_target_register(DND_FILES) + self.dnd_bind("<>", self.filedrop) + + def openpreview(self): + self.manager.openpreview() + if self.manager.getcurrentchild().ispreviewed: + self.filemenu.entryconfigure(5, image=self.checkimg) + else: self.filemenu.entryconfigure(6, image="") + + def filedrop(self, event): + self.file = open(event.data.replace("{", "").replace("}", ""), "r") + self.manager.newtab(self.file) + self.file.close() + + def openproperties(self): + self.filetoopen = self.manager.getcurrentchild().filedir.cget("text") + + if isfile(self.filetoopen): + self.properties = Properties(self.filetoopen, self) + self.wait_window(self.properties) + + if isfile(newfile): + self.manager.forget(self.manager.select()) + self.manager.newtab(open(newfile, "r")) + +class Properties(Toplevel): + def __init__(self, file, *args): + super().__init__(*args) + + self.file = file + + self.title("File Properties") + self.geometry("350x175") + self.resizable(False, False) + + self.imagefile = "assets/filetypes/{}_{}.png".format(file.split(".")[-1], theme) + if isfile(self.imagefile): self.image = PhotoImage(file=self.imagefile) + else: self.image = PhotoImage(file="assets/filetypes/other_{}.png".format(theme)) + self.imagelabel = Label(self, image=self.image).place(x=5, y=5) + + self.filename = Entry(self, width=25) + self.filename.insert(0, file.split("/")[-1]) + self.filename.pack(anchor="ne", padx=15, pady=20) + + self.filepath = Label(self, text="/".join(file.split("/")[:-1]), font=("Segoe UI", 10), width=27, anchor="w") + self.filepath.pack(anchor="ne", padx=15) + + self.btnframe = Frame(self, width=320, height=40) + self.btnframe.pack_propagate(False) + + self.cancelbtn = Button(self.btnframe, text="Cancel", command=self.cancel, width=14).pack(side="left") + self.applybtn = Button(self.btnframe, text="Apply", command=self.apply, width=14).pack(side="right") + + self.btnframe.pack(anchor="nw", padx=15, pady=15) + + def cancel(self): + global newfile + + newfile = "" + + self.destroy() + + def apply(self): + global newfile # im sorry + + for i in BLOCKEDCHARS: + if i in self.filename.get(): + show_message(title="Invalid File Name", details="\nA File Name cannot contain one of the following characters:\n\n{}".format(BLOCKEDCHARS)) + return + + newfile = self.file.split("/") + newfile[-1] = self.filename.get() + newfile = "/".join(newfile) + + rename(self.file, newfile) + + self.destroy() + + + + +if __name__ == "__main__": + main = App() + main.mainloop() diff --git a/src/mdpreview.py b/src/mdpreview.py deleted file mode 100644 index 1a36dec..0000000 --- a/src/mdpreview.py +++ /dev/null @@ -1,36 +0,0 @@ -import markdown -from tkinterweb.htmlwidgets import HtmlFrame - -import vars as v - - -def update(): - html = markdown.markdown(v.textwidget.get("1.0", "end")) - display.load_html(html) - - -def reload(e): - v.root.after(2, update) - - -def build(): - global display, binding - - display = HtmlFrame(v.root, messages_enabled=False) - display.place( - x=v.root.winfo_width() / 2, - y=50, - width=v.root.winfo_width() / 2, - height=v.root.winfo_height() - 75, - ) - display.on_link_click(reload) # This line blocks clicking on links - - v.textwidget.bind("", reload, add="+") - v.textwidget.bind("", reload, add="+") - reload("") - - -def close(): - v.textwidget.unbind("") - v.textwidget.unbind("") - display.destroy() diff --git a/src/pages/about.py b/src/pages/about.py deleted file mode 100644 index 867d548..0000000 --- a/src/pages/about.py +++ /dev/null @@ -1,28 +0,0 @@ -import tkinter -import webbrowser -from tkinter import messagebox, ttk - -import update -import utils as u -import vars as v - - -def checkforupdates(): - response = update.check() - - if response == True: webbrowser.open("https://github.com/futura-py/notes/releases") - elif response == False: messagebox.showinfo(title="Update", message="You are on the newest version of Futura Notes!") - else: messagebox.showinfo(title="Rate Limit", message="You have managed to exceed the github api rate limit of 60 requests per hour. idk how that can be achieved by accident. try again in an hour i guess.",) - - -def build(): - root = tkinter.Toplevel() - root.title("About Futura Notes") - root.geometry("650x200") - root.resizable(False, False) - - name = tkinter.Label(root, text="Futura Notes", font=("Segoe UI", 40, "bold")).place(x=30, y=15) - version = tkinter.Label(root, text="Version {}".format(v.ver.split(" ")[0]), font=("Segoe UI", 20, "")).place(x=370, y=42) - versiontype = tkinter.Label(root, text="Beta" if v.ver.endswith("beta") else "Stable", font=("Segoe UI", 20, ""), fg="orange" if v.ver.endswith("beta") else "green").place(x=510, y=42) - github = ttk.Button(root, text=" Github Repo", image=v.github_light if u.dark() else v.github_dark, compound="left", command=lambda: webbrowser.open("https://github.com/futura-py/notes")).place(x=30, y=105) - updatebtn = ttk.Button(root, text=" Check for Updates", image=v.update_light if u.dark() else v.update_dark, compound="left", command=checkforupdates).place(x=170, y=105) diff --git a/src/pages/filetype.py b/src/pages/filetype.py deleted file mode 100644 index 91e8809..0000000 --- a/src/pages/filetype.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import tkinter -from tkinter import ttk - -import ntkutils - -from generatesize import system - - -def changetype(filename): - global filetype - - def change(): - global new_path - if entry.get().startswith("."): - new_path = ( - filename.removesuffix("." + filename.split(".")[-1]) + entry.get() - ) - os.rename(filename, new_path) - filetype.destroy() - else: - print("not an extension") - - filetype = tkinter.Toplevel() - if system != "Darwin": - ntkutils.dark_title_bar(filetype) - filetype.title("Futura Notes - Change file type") - lbl = tkinter.Label(filetype, text="Change file extension:", font=("", 20)).pack( - pady=5 - ) - entry = ttk.Entry(filetype) - entry.pack(pady=5) - btn = ttk.Button(filetype, text="Apply", command=change).pack(pady=5) - - -def get(path): - changetype(path) - filetype.wait_window() - return new_path diff --git a/src/pages/wanttosave.py b/src/pages/wanttosave.py deleted file mode 100644 index c9ec0ed..0000000 --- a/src/pages/wanttosave.py +++ /dev/null @@ -1,24 +0,0 @@ -import tkinter -from tkinter import ttk - -import tabmanager as t -import vars as v - - -def save(e=""): - t.save() - v.root.destroy() - - -def build(): - w = tkinter.Toplevel() - w.geometry("300x100") - w.title("Save before exiting?") - w.focus_set() - - lbl = tkinter.Label(w, font=("Segoe UI", 10, "bold"), text="Do you want to save before exiting?").pack(pady=10) - - btnno = ttk.Button(w, text="No", command=v.root.destroy, width=10).place(x=25, y=50) - btnyes = ttk.Button(w, text="Yes", command=save, width=10, style="Accent.TButton").place(x=170, y=50) - - w.bind("", save) \ No newline at end of file diff --git a/src/settings/UI.py b/src/settings/UI.py deleted file mode 100644 index 1a603b4..0000000 --- a/src/settings/UI.py +++ /dev/null @@ -1,271 +0,0 @@ -import tkinter -from tkinter import font, ttk - -import ntkutils -import sv_ttk - -import config -import utils as u -import vars as v -from generatesize import system - -options = [ - "Theme", - "Font", - "Font Size", - "Display Line Numbers", - "Syntax Highlighting", - "Hotkeys", - "Mica Blur", -] - -options2 = { - "Theme": "appearance", - "Font": "appearance", - "Font Size": "appearance", - "Display Line Numbers": "general", - "Syntax Highlighting": "general", - "Hotkeys": "hotkeys", - "Mica Blur": "experimental", - "On Close": "general", -} - -def general(): - global page, btnnumbers, btnhighlight, boxonclose - - savechanges() - clearstates() - - btngeneral.configure(style="Accent.TButton") - if u.dark(): btngeneral.configure(image=v.settings_dark) - else: btngeneral.configure(image=v.settings_light) - - ntkutils.clearwin(frameright) - - page = "general" - - lblonclose = tkinter.Label(frameright, text="On Close:").place(x=10, y=15) - boxonclose = ttk.Combobox(frameright, values=["Do Nothing", "Save", "Ask"], state="readonly", width=25) - boxonclose.set(cfg["onclose"]) - boxonclose.pack(padx=10, pady=10, anchor="e") - - lblnumbers = tkinter.Label(frameright, text="Display line numbers:").place(x=10, y=60) - btnnumbers = ttk.Checkbutton(frameright, style="Switch.TCheckbutton") - btnnumbers.pack(padx=10, pady=10, anchor="e") - btnnumbers.state(["!alternate"]) - if cfg["linenumbers"]: btnnumbers.state(["!alternate", "selected"]) - - lblhighlight = tkinter.Label(frameright, text="Syntax Highlighting:").place(x=10, y=115) - btnhighlight = ttk.Checkbutton(frameright, style="Switch.TCheckbutton") - btnhighlight.pack(padx=10, pady=15, anchor="e") - btnhighlight.state(["!alternate"]) - if cfg["syntax-highlighting"]: btnhighlight.state(["!alternate", "selected"]) - -def appearance(): - global boxtheme, boxfont, boxsize, page - - savechanges() - clearstates() - - btnappearence.configure(style="Accent.TButton", image=eval("v.brush_" + sv_ttk.get_theme())) - - ntkutils.clearwin(frameright) - - page = "appearance" - - lbltheme = tkinter.Label(frameright, text="Theme:").place(x=10, y=15) - boxtheme = ttk.Combobox(frameright, values=["Dark", "Light", "System"], state="readonly", width=25) - boxtheme.set(cfg["theme"]) - boxtheme.pack(padx=10, pady=10, anchor="e") - - lblfont = tkinter.Label(frameright, text="Font:").place(x=10, y=60) - boxfont = ttk.Combobox(frameright, state="readonly", values=fonts, width=15) - boxfont.set(cfg["font"]) - boxfont.pack(padx=70, pady=10, anchor="e") - boxsize = ttk.Entry(frameright, width=5) - boxsize.insert(0, cfg["font-size"]) - boxsize.place(x=265, y=58) - - -def experimental(): - global page, btnmica, btnhotkeys - - savechanges() - clearstates() - - btnexperimental.configure(style="Accent.TButton", image=eval("v.warn_" + sv_ttk.get_theme())) - - ntkutils.clearwin(frameright) - - page = "experimental" - - lblmica = tkinter.Label(frameright, text="Mica Blur:").place(x=10, y=12) - btnmica = ttk.Checkbutton(frameright, style="Switch.TCheckbutton") - btnmica.pack(padx=10, pady=10, anchor="e") - btnmica.state(["!alternate"]) - if cfg["mica"]: btnmica.state(["!alternate", "selected"]) - - -def hotkeys(): - global page, entryopen, entrysave - - savechanges() - clearstates() - - btnhotkeys.configure(style="Accent.TButton", image=eval("v.keyboard_" + sv_ttk.get_theme())) - - ntkutils.clearwin(frameright) - - page = "hotkeys" - - lblopen = tkinter.Label(frameright, text="Open:").place(x=10, y=12) - entryopen = ttk.Entry(frameright, width=25) - entryopen.insert(0, cfg["hkey-open"]) - entryopen.pack(padx=10, pady=10, anchor="e") - - lblsave = tkinter.Label(frameright, text="Save:").place(x=10, y=67) - entrysave = ttk.Entry(frameright, width=25) - entrysave.insert(0, cfg["hkey-save"]) - entrysave.pack(padx=10, pady=10, anchor="e") - - -def savechanges(): - if page == "general": - cfg["linenumbers"] = btnnumbers.instate(["selected"]) - cfg["syntax-highlighting"] = btnhighlight.instate(["selected"]) - cfg["onclose"] = boxonclose.get() - - if u.dark(): btngeneral.configure(image=v.settings_light) - else: btngeneral.configure(image=v.settings_dark) - elif page == "appearance": - cfg["theme"] = boxtheme.get() - cfg["font"] = boxfont.get() - cfg["font-size"] = boxsize.get() - - if u.dark(): btnappearence.configure(image=v.brush_light) - else: btnappearence.configure(image=v.brush_dark) - elif page == "experimental": - cfg["mica"] = btnmica.instate(["selected"]) - - if u.dark(): btnexperimental.configure(image=v.warn_light) - else: btnexperimental.configure(image=v.warn_dark) - elif page == "hotkeys": - cfg["hkey-open"] = entryopen.get() - cfg["hkey-save"] = entrysave.get() - - if u.dark(): btnhotkeys.configure(image=v.keyboard_light) - else: btnhotkeys.configure(image=v.keyboard_dark) - - -def apply(): - global page, save - - savechanges() - - ntkutils.cfgtools.SaveCFG(cfg) - settings.destroy() - - -def build(): - global frameright, frameleft, btnappearence, btnexperimental, btnhotkeys, settings, page, cfg, btngeneral - - cfg = config.get() - page = "" - - settings = tkinter.Toplevel() - ntkutils.windowsetup(settings, "Futura Notes - Settings", "assets/logo.png", False, "500x400") - if system != "Darwin" and u.dark(): - ntkutils.dark_title_bar(settings) - - frameleft = tkinter.Frame(settings, width=175, bg=v.theme["secondary"]) - frameleft.pack(side=tkinter.LEFT, fill="y") - frameleft.pack_propagate(False) - - frameright = tkinter.Frame(settings, width=325) - frameright.pack(side=tkinter.LEFT, fill="both") - frameright.pack_propagate(False) - - def update(data): - menu.delete(0, "end") - for value in data: - menu.insert("end", value) - menu.configure(height=len(data)) - - def check(e): - v = search.get() - if v == "": - data = options - else: - data = [] - for item in options: - if v.lower() in item.lower(): - data.append(item) - update(data) - - def showlist(e): - search.delete(0, "end") - menu.bind("<>", onselect) - menu.place(x=search.winfo_x(), y=search.winfo_y() + search.winfo_height()) - check("") - - def removelist(e): - menu.unbind("<>") - menu.place(x=1000, y=1000) - - search = ttk.Entry(frameleft, width=23) - search.pack(pady=10) - search.bind("", check) - search.update() - search.bind("", showlist) - search.bind("", removelist) - search.insert(0, "Search for a setting...") - - def onselect(evt): - w = evt.widget - index = int(w.curselection()[0]) - value = w.get(index) - func = eval(options2[value]) - func() - - btngeneral = ttk.Button(frameleft, text="General", width=14, command=general, image=v.settings_dark, compound="left") - btngeneral.pack(pady=10) - - btnappearence = ttk.Button(frameleft, text="Appearence", width=14, command=appearance, image=v.brush_dark, compound="left") - btnappearence.pack() - - btnhotkeys = ttk.Button(frameleft, text="Hotkeys", width=14, command=hotkeys, image=v.keyboard_dark, compound="left") - btnhotkeys.pack(pady=10) - - btnexperimental = ttk.Button(frameleft, text="Unstable Features", width=14, command=experimental, image=v.warn_dark, compound="left") - btnexperimental.pack() - - btnapply = ttk.Button(frameleft, text="Apply", style="Accent.TButton", width=18, command=apply) - btnapply.pack(side="bottom", pady=10) - - lblrestart = tkinter.Label(frameleft, text="Restart required!", wraplength=170, fg="grey", bg=v.theme["secondary"]).pack(side="bottom") - - menu = tkinter.Listbox(frameleft, width=23) - menu.bind("<>", onselect) - - if u.dark(): - btngeneral.configure(image=v.settings_light) - btnappearence.configure(image=v.brush_light) - btnhotkeys.configure(image=v.keyboard_light) - btnexperimental.configure(image=v.warn_light) - - getfonts() - general() - - -def clearstates(): - for i in frameleft.pack_slaves(): - try: - i.configure(style="TButton") - except: - pass - - -def getfonts(): - global fonts - fonts = list(font.families()) - fonts.sort() diff --git a/src/settings/images.py b/src/settings/images.py deleted file mode 100644 index 547ec0b..0000000 --- a/src/settings/images.py +++ /dev/null @@ -1,19 +0,0 @@ -import tkinter - -import vars as v - - -def setimages(): - v.brush_light = tkinter.PhotoImage(master=v.root, file="./assets/brush_light.png") - v.brush_dark = tkinter.PhotoImage(master=v.root, file="./assets/brush_dark.png") - v.keyboard_light = tkinter.PhotoImage(master=v.root, file="./assets/keyboard_light.png") - v.keyboard_dark = tkinter.PhotoImage(master=v.root, file="./assets/keyboard_dark.png") - v.warn_light = tkinter.PhotoImage(master=v.root, file="./assets/warn_light.png") - v.warn_dark = tkinter.PhotoImage(master=v.root, file="./assets/warn_dark.png") - v.logo = tkinter.PhotoImage(master=v.root, file="./assets/logo.png") - v.github_dark = tkinter.PhotoImage(master=v.root, file="./assets/github_dark.png") - v.github_light = tkinter.PhotoImage(master=v.root, file="./assets/github_light.png") - v.update_dark = tkinter.PhotoImage(master=v.root, file="./assets/update_dark.png") - v.update_light = tkinter.PhotoImage(master=v.root, file="./assets/update_light.png") - v.settings_light = tkinter.PhotoImage(master=v.root, file="./assets/settings_light.png") - v.settings_dark = tkinter.PhotoImage(master=v.root, file="./assets/settings_dark.png") diff --git a/src/tabmanager.py b/src/tabmanager.py deleted file mode 100644 index 3c0acf2..0000000 --- a/src/tabmanager.py +++ /dev/null @@ -1,168 +0,0 @@ -import tkinter -from tkinter import filedialog - -import pygments.lexers -from pygments.lexers import get_lexer_for_filename - -import pages.filetype as f -import vars as v - -tabs = [["Untitled", "", "unsaved", "*"]] - -# Item 0: Name -# Item 1: Content -# Item 2: Storage Path -# Item 3: Save Status ("*" or "") - - -def updatetab(file): - tabs[v.tabselected][0] = file.name.split("/")[-1] - tabs[v.tabselected][2] = file.name - tabs[v.tabselected][3] = "" - - -def updatetitle(): - v.root.title("Futura Notes - {} {}".format(tabs[v.tabselected][0], tabs[v.tabselected][3])) - - -def redrawlinenums(): - if v.cfg["linenumbers"]: - v.textwidget.linenums.redraw() - - -def new(): - tabs[v.tabselected][1] = v.textwidget.get("1.0", "end") # Save edits - v.textwidget.delete("1.0", "end") - tabs.append(["Untitled", "", "unsaved", "*"]) - v.filedir.configure(text="unsaved") - v.tabselected += 1 - - v.tabbar.add(tkinter.Frame(), text=tabs[v.tabselected][0], image=v.closeimg, compound="right") - v.tabbar.select(v.tabselected) - - updatetitle() - if v.cfg["syntax-highlighting"]: - v.textwidget._set_lexer(pygments.lexers.TextLexer) - - -def save(e="", saveas=False): - if tabs[v.tabselected][2] == "unsaved" or saveas: - file = filedialog.asksaveasfile() - if file == None: - return - else: - file = open(tabs[v.tabselected][2], "w") - - if file != None: - file.write(v.textwidget.get("1.0", "end")) - - updatetab(file) - v.filedir.configure(text=file.name) - - file.close() - - updatetitle() - setlexer() - - -def openfile(e="", path=""): - if path == "": - file = filedialog.askopenfile() - content = file.read() - else: - file = open(path, "r") - content = file.read() - - isopen = False - - for i in tabs: - if i[2] == file.name: isopen=True - - if not isopen: - if v.textwidget.get("1.0", "end").replace("\n", "") != "": - new() - - updatetab(file) - - file.close() - - v.tabbar.tab(v.tabselected, text=tabs[v.tabselected][0], image=v.closeimg, compound="right") - v.textwidget.insert("1.0", content) - v.filedir.configure(text=tabs[v.tabselected][2]) - - updatetitle() - setlexer() - redrawlinenums() - - -def opentab(event, tabdeleted=False): - if not tabdeleted: - tabs[v.tabselected][1] = v.textwidget.get("1.0", "end") - - v.tabselected = v.tabbar.index(v.tabbar.select()) - - v.textwidget.delete("1.0", "end") - v.textwidget.insert("1.0", tabs[v.tabselected][1]) - v.textwidget.delete("end-1c", "end") - - v.filedir.configure(text=tabs[v.tabselected][2]) - - updatetitle() - setlexer() - redrawlinenums() - - -def setlexer(): - if v.cfg["syntax-highlighting"]: - try: - lexer = get_lexer_for_filename(tabs[v.tabselected][0]) - except pygments.util.ClassNotFound: - lexer = pygments.lexers.TextLexer - lexer = "pygments.lexers." + str(lexer).split(".")[-1].removesuffix(">").removesuffix("'") - v.textwidget._set_lexer(eval(lexer)) - - -# The following two functions contain code copied from https://github.com/Akuli/porcupine - - -def closetab(event): - before = v.tabbar.index(f"@{event.x},{event.y}") - after = before + 1 - - if v.tabbar.index(v.tabbar.tabs()[before:after][0]) < v.tabselected: - v.tabselected -= 1 - - tabs.pop(v.tabbar.index(v.tabbar.tabs()[before:after][0])) - v.tabbar.forget(v.tabbar.tabs()[before:after][0]) - opentab(event, True) - - -def click(event) -> None: - if event.widget.identify(event.x, event.y) == "label": - # find the right edge of the top label (including close button) - right = event.x - while event.widget.identify(right, event.y) == "label": - right += 1 - - if event.x >= right - v.closeimg.width(): - if event.widget.index("end") != 1: - closetab(event) - else: - v.root.destroy() - else: - opentab(event) - else: - opentab(event) - - -def changetype(): - if tabs[v.tabselected][2] == "unsaved": - save() - else: - result = f.get(tabs[v.tabselected][2]) - tabs[v.tabselected][2] = result - tabs[v.tabselected][0] = result.split("/")[-1] - v.filedir.configure(text=result) - - updatetitle() - setlexer() diff --git a/src/themes/dark.py b/src/themes/dark.py deleted file mode 100644 index 9cf0409..0000000 --- a/src/themes/dark.py +++ /dev/null @@ -1,11 +0,0 @@ -theme = { - "primary": "#1c1c1c", - "secondary": "#202020", - "opposite_secondary": "#f3f3f3", - "color_scheme": "ayu-dark", - "closeimg": "assets/close_light.png", -} - - -def get(): - return theme diff --git a/src/themes/light.py b/src/themes/light.py deleted file mode 100644 index a8fea9e..0000000 --- a/src/themes/light.py +++ /dev/null @@ -1,11 +0,0 @@ -theme = { - "primary": "#fafafa", - "secondary": "#f3f3f3", - "opposite_secondary": "#202020", - "color_scheme": "ayu-light", - "closeimg": "assets/close_dark.png", -} - - -def get(): - return theme diff --git a/src/update.py b/src/update.py deleted file mode 100644 index 801a167..0000000 --- a/src/update.py +++ /dev/null @@ -1,21 +0,0 @@ -import requests - -import vars as v - - -def install(): - pass - - -def check(): - api_response = requests.get("https://api.github.com/repos/futura-py/notes/releases") - - try: - latest_tag = next(iter(api_response.json()))["tag_name"] - - if float(str(latest_tag).removeprefix("v")) > float(v.ver.split(" ")[0]): - return True - else: - return False - except: - return "rate limit" diff --git a/src/utils.py b/src/utils.py deleted file mode 100644 index 3a88d8f..0000000 --- a/src/utils.py +++ /dev/null @@ -1,10 +0,0 @@ -import darkdetect - -import vars as v - - -def dark(): - if v.cfg["theme"] == "Dark" or (v.cfg["theme"] == "System" and darkdetect.isDark()): - return True - else: - return False \ No newline at end of file diff --git a/src/vars.py b/src/vars.py deleted file mode 100644 index 4d28283..0000000 --- a/src/vars.py +++ /dev/null @@ -1,30 +0,0 @@ -tabselected = 0 - -cfg = [] - -root = "" -textwidget = "" -filedir = "" -closeimg = "" -normal = "" -selected = "" -normal_hover = "" -selected_hover = "" -tabbar = "" -footer = "" -theme = "" -ver = "" - -brush_light = "" -brush_dark = "" -keyboard_light = "" -keyboard_dark = "" -warn_light = "" -warn_dark = "" -logo = "" -github_dark = "" -github_light = "" -update_dark = "" -update_light = "" -settings_light = "" -settings_dark = ""