17 Commits
pre-v1 ... v1.1

Author SHA1 Message Date
admin
03b05ecc08 v1.1 2026-05-24 10:54:17 +00:00
admin
34db86fdac ui v1.1 2026-05-24 10:53:42 +00:00
admin
ed74e97693 config v1.1 2026-05-24 10:53:09 +00:00
admin
9eb1d2cc88 app v1.1 2026-05-24 10:52:44 +00:00
admin
9a64fecd3d update v1.1 2026-05-24 10:52:18 +00:00
admin
904e8ba82b update v1 2026-05-24 08:22:12 +00:00
admin
a80d28c616 Update README.md 2026-05-24 03:34:21 +00:00
admin
aa5dadc302 config v1 fix 2026-05-24 03:19:38 +00:00
admin
7d67d4a74d start v1 fix 2026-05-24 03:19:10 +00:00
admin
d129c70c10 app v1 fix 2026-05-24 03:18:05 +00:00
admin
f962dd393e v1 2026-05-24 01:57:24 +00:00
admin
573c08a07c ui v1 2026-05-24 01:54:51 +00:00
admin
5270ef92c1 config v1 2026-05-24 01:54:31 +00:00
admin
8c7b9fe521 app v1 2026-05-24 01:54:09 +00:00
admin
599efb060e v1 2026-05-24 01:53:17 +00:00
admin
781283e628 v1 ui 2026-05-24 01:50:36 +00:00
admin
214531fc8e start v1 2026-05-24 01:50:04 +00:00
8 changed files with 667 additions and 400 deletions

View File

@@ -1,3 +1,10 @@
# overlaynote ## overlaynote
desktop note overlay
v1.1
minimal note overlay ---
*v1.1* <br>
<a href="https://git.backend-3.com/admin/overlaynote/releases/download/v1.0/overlaynote-v1.1.zip" download>download</a>
---
<img width="790vw" src="https://git.backend-3.com/admin/i/raw/branch/main/1etagdzc.png" >

View File

@@ -44,7 +44,7 @@ THEMES: Final[list[ThemePalette]] = [
), ),
ThemePalette( ThemePalette(
name="yellow", name="yellow",
background="rgba(113,63,18,0.24)", background="rgba(113,93,38,0.24)",
background_inactive="rgba(113,63,18,0.16)", background_inactive="rgba(113,63,18,0.16)",
border_active="rgba(250,204,21,0.36)", border_active="rgba(250,204,21,0.36)",
border_inactive="rgba(255,255,255,0.10)", border_inactive="rgba(255,255,255,0.10)",

View File

@@ -1,34 +1,62 @@
# v3.1.1 settings.py # v4.0.0 settings.py
import json import json
import sys
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Final from typing import Final
def resolve_app_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(
sys.executable
).resolve().parent
return (
Path(__file__)
.resolve()
.parent
.parent
)
APP_DIR: Final[Path] = ( APP_DIR: Final[Path] = (
Path(__file__).resolve().parent.parent resolve_app_dir()
)
APP_DIR.mkdir(
parents=True,
exist_ok=True,
) )
SETTINGS_FILE: Final[Path] = ( SETTINGS_FILE: Final[Path] = (
APP_DIR / "settings.json" APP_DIR / "settings.json"
) )
STATE_FILE: Final[Path] = (
APP_DIR / "active.json"
)
MARKDOWN_FILE: Final[Path] = (
APP_DIR / "note.md"
)
DEFAULT_SETTINGS: Final[dict] = { DEFAULT_SETTINGS: Final[dict] = {
"active_profile": "default", "active_profile": "default",
"profiles": { "profiles": {
"default": { "default": {
"theme": "macos_blue", "theme": "dark",
"window": { "window": {
"width": 260, "width": 250,
"height": 300, "height": 260,
"min_width": 220, "min_width": 200,
"min_height": 140, "min_height": 100,
"layout_margin": 4, "layout_margin": 0,
"layout_spacing": 4, "layout_spacing": 0,
"drag_handle_height": 28, "drag_handle_height": 0,
"resize_grip_size": 16, "resize_grip_size": 16,
"corner_radius": 16, "corner_radius": 8,
"startup_stabilize_ms": 80, "startup_stabilize_ms": 80,
}, },
"appearance": { "appearance": {
@@ -36,10 +64,10 @@ DEFAULT_SETTINGS: Final[dict] = {
"enable_acrylic": True, "enable_acrylic": True,
"enable_glass_blur": True, "enable_glass_blur": True,
"enable_animations": True, "enable_animations": True,
"enable_shadow": True, "enable_shadow": False,
"opacity_active": 1.0, "opacity_active": 0.97,
"opacity_inactive": 0.96, "opacity_inactive": 0.95,
"opacity_dragging": 0.92, "opacity_dragging": 0.89,
"focus_animation_ms": 180, "focus_animation_ms": 180,
}, },
"editor": { "editor": {
@@ -58,7 +86,7 @@ DEFAULT_SETTINGS: Final[dict] = {
"enable_clickable_links": True, "enable_clickable_links": True,
"enable_antialiasing": True, "enable_antialiasing": True,
"enable_spellcheck": False, "enable_spellcheck": False,
"padding": 8, "padding": 0,
}, },
"behavior": { "behavior": {
"enable_tray": True, "enable_tray": True,
@@ -68,127 +96,49 @@ DEFAULT_SETTINGS: Final[dict] = {
"high_contrast_inactive": False, "high_contrast_inactive": False,
}, },
}, },
"light": {
"theme": "light",
"window": {
"width": 260,
"height": 300,
"min_width": 220,
"min_height": 140,
"layout_margin": 4,
"layout_spacing": 4,
"drag_handle_height": 28,
"resize_grip_size": 16,
"corner_radius": 16,
"startup_stabilize_ms": 80,
},
"appearance": {
"enable_mica": False,
"enable_acrylic": False,
"enable_glass_blur": False,
"enable_animations": True,
"enable_shadow": False,
"opacity_active": 1.0,
"opacity_inactive": 0.98,
"opacity_dragging": 0.96,
"focus_animation_ms": 120,
},
"editor": {
"font_family": [
"Inter",
"Segoe UI",
"Arial",
"system-ui",
],
"font_size": 13,
"preview_delay_ms": 500,
"save_debounce_ms": 400,
"max_note_size": 2_000_000,
"enable_markdown_preview": True,
"enable_clickable_links": True,
"enable_antialiasing": True,
"enable_spellcheck": False,
"padding": 8,
},
"behavior": {
"enable_tray": True,
"enable_persistence": True,
"enable_window_memory": True,
"enable_auto_restore": True,
"high_contrast_inactive": False,
},
},
"accessibility": {
"theme": "dark",
"window": {
"width": 320,
"height": 380,
"min_width": 260,
"min_height": 180,
"layout_margin": 6,
"layout_spacing": 6,
"drag_handle_height": 32,
"resize_grip_size": 18,
"corner_radius": 18,
"startup_stabilize_ms": 80,
},
"appearance": {
"enable_mica": False,
"enable_acrylic": False,
"enable_glass_blur": False,
"enable_animations": False,
"enable_shadow": False,
"opacity_active": 1.0,
"opacity_inactive": 1.0,
"opacity_dragging": 1.0,
"focus_animation_ms": 0,
},
"editor": {
"font_family": [
"Inter",
"Segoe UI",
"Arial",
"system-ui",
],
"font_size": 15,
"preview_delay_ms": 300,
"save_debounce_ms": 300,
"max_note_size": 2_000_000,
"enable_markdown_preview": True,
"enable_clickable_links": True,
"enable_antialiasing": True,
"enable_spellcheck": False,
"padding": 10,
},
"behavior": {
"enable_tray": True,
"enable_persistence": True,
"enable_window_memory": True,
"enable_auto_restore": True,
"high_contrast_inactive": True,
},
},
}, },
} }
def atomic_write(
path: Path,
content: str,
) -> None:
path.parent.mkdir(
parents=True,
exist_ok=True,
)
temp = path.with_suffix(
f"{path.suffix}.tmp"
)
temp.write_text(
content,
encoding="utf-8",
)
temp.replace(path)
@dataclass(slots=True) @dataclass(slots=True)
class SettingsManager: class SettingsManager:
settings: dict settings: dict
@classmethod @classmethod
def load(cls) -> "SettingsManager": def load(cls) -> "SettingsManager":
SETTINGS_FILE.parent.mkdir(
parents=True,
exist_ok=True,
)
if not SETTINGS_FILE.exists(): if not SETTINGS_FILE.exists():
SETTINGS_FILE.write_text( atomic_write(
SETTINGS_FILE,
json.dumps( json.dumps(
DEFAULT_SETTINGS, DEFAULT_SETTINGS,
indent=2, indent=2,
), ),
encoding="utf-8",
)
return cls(
DEFAULT_SETTINGS
) )
try: try:
@@ -203,9 +153,21 @@ class SettingsManager:
data, data,
) )
return cls(merged) manager = cls(merged)
manager.save()
return manager
except Exception: except Exception:
atomic_write(
SETTINGS_FILE,
json.dumps(
DEFAULT_SETTINGS,
indent=2,
),
)
return cls( return cls(
DEFAULT_SETTINGS DEFAULT_SETTINGS
) )
@@ -245,12 +207,12 @@ class SettingsManager:
return result return result
def save(self) -> None: def save(self) -> None:
SETTINGS_FILE.write_text( atomic_write(
SETTINGS_FILE,
json.dumps( json.dumps(
self.settings, self.settings,
indent=2, indent=2,
), ),
encoding="utf-8",
) )
@property @property
@@ -284,22 +246,6 @@ class SettingsManager:
return profiles[active] return profiles[active]
def set_active_profile(
self,
profile_name: str,
) -> None:
if (
profile_name
not in self.profiles
):
return
self.settings[
"active_profile"
] = profile_name
self.save()
def reload(self) -> None: def reload(self) -> None:
loaded = ( loaded = (
SettingsManager.load() SettingsManager.load()
@@ -315,43 +261,3 @@ class SettingsManager:
) )
self.save() self.save()
def create_profile(
self,
name: str,
data: dict,
) -> None:
self.settings[
"profiles"
][name] = data
self.save()
def delete_profile(
self,
name: str,
) -> None:
if name == "default":
return
if (
name
not in self.settings[
"profiles"
]
):
return
del self.settings[
"profiles"
][name]
if (
self.active_profile_name
== name
):
self.settings[
"active_profile"
] = "default"
self.save()

View File

@@ -1,15 +1,15 @@
# v3.1.0 app.py # v4.2.1 app.py
import contextlib import atexit
import json import json
import logging import logging
import os import os
import signal
import subprocess import subprocess
import sys import sys
import traceback
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum, auto from enum import Enum, auto
from hashlib import sha256 from hashlib import sha256
from pathlib import Path
from typing import Final
from PyQt6.QtCore import ( from PyQt6.QtCore import (
QEasingCurve, QEasingCurve,
@@ -33,6 +33,7 @@ from PyQt6.QtGui import (
) )
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication,
QFrame, QFrame,
QMenu, QMenu,
QSizeGrip, QSizeGrip,
@@ -45,10 +46,16 @@ from PyQt6.QtWidgets import (
from qframelesswindow import AcrylicWindow from qframelesswindow import AcrylicWindow
from config.profiles import THEMES from config.profiles import THEMES
from config.settings import ( from config.settings import (
APP_DIR, APP_DIR,
MARKDOWN_FILE,
SETTINGS_FILE,
STATE_FILE,
SettingsManager, SettingsManager,
atomic_write,
) )
from ui.editor import LinkTextEdit from ui.editor import LinkTextEdit
from ui.styles import build_stylesheet from ui.styles import build_stylesheet
@@ -59,15 +66,6 @@ logging.basicConfig(
) )
STATE_FILE: Final[Path] = (
APP_DIR / "sticky_state.json"
)
MARKDOWN_FILE: Final[Path] = (
APP_DIR / "sticky.md"
)
class WindowState(Enum): class WindowState(Enum):
IDLE = auto() IDLE = auto()
DRAGGING = auto() DRAGGING = auto()
@@ -83,9 +81,8 @@ class ActiveState(Enum):
class PersistedState: class PersistedState:
x: int = 100 x: int = 100
y: int = 100 y: int = 100
width: int = 260 width: int = 250
height: int = 300 height: int = 260
theme_index: int = 0
class DragHandle(QFrame): class DragHandle(QFrame):
@@ -138,12 +135,15 @@ class StickyNoteApp(AcrylicWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._closing = False self._is_quitting = False
self._force_close = False
self.settings_manager = ( self.settings_manager = (
SettingsManager.load() SettingsManager.load()
) )
self.ensure_runtime_files()
self.profile = ( self.profile = (
self.settings_manager.profile self.settings_manager.profile
) )
@@ -158,6 +158,10 @@ class StickyNoteApp(AcrylicWindow):
self.drag_offset = QPoint() self.drag_offset = QPoint()
self.drag_origin = QPoint()
self.pending_drag = False
self.last_saved_hash = "" self.last_saved_hash = ""
self.last_preview_hash = "" self.last_preview_hash = ""
@@ -166,25 +170,68 @@ class StickyNoteApp(AcrylicWindow):
self.configure_font() self.configure_font()
self.build_ui()
self.setup_timers() self.setup_timers()
self.build_ui()
self.configure_window() self.configure_window()
self.build_tray() QTimer.singleShot(
80,
self.restore_state() self.restore_state,
)
self.apply_style()
QTimer.singleShot( QTimer.singleShot(
self.profile["window"][ 120,
"startup_stabilize_ms" self.initialize_effects,
], )
QTimer.singleShot(
220,
self.build_tray,
)
QTimer.singleShot(
260,
self.apply_style,
)
QTimer.singleShot(
320,
self.finalize_startup, self.finalize_startup,
) )
self.register_shutdown_hooks()
def ensure_runtime_files(self) -> None:
APP_DIR.mkdir(
parents=True,
exist_ok=True,
)
if not SETTINGS_FILE.exists():
self.settings_manager.save()
if not MARKDOWN_FILE.exists():
atomic_write(
MARKDOWN_FILE,
"",
)
if not STATE_FILE.exists():
atomic_write(
STATE_FILE,
json.dumps(
{
"x": 100,
"y": 100,
"width": 250,
"height": 260,
},
indent=2,
),
)
def configure_font(self) -> None: def configure_font(self) -> None:
self.font_object = QFont() self.font_object = QFont()
@@ -232,6 +279,16 @@ class StickyNoteApp(AcrylicWindow):
self.show_preview self.show_preview
) )
self.drag_timer = QTimer(self)
self.drag_timer.setSingleShot(
True
)
self.drag_timer.timeout.connect(
self.activate_delayed_drag
)
self.opacity_anim = ( self.opacity_anim = (
QPropertyAnimation( QPropertyAnimation(
self, self,
@@ -262,10 +319,14 @@ class StickyNoteApp(AcrylicWindow):
window["min_height"], window["min_height"],
) )
self.setWindowFlags( self.setWindowFlag(
Qt.WindowType.Tool Qt.WindowType.WindowStaysOnTopHint,
| Qt.WindowType.WindowStaysOnTopHint True,
| Qt.WindowType.FramelessWindowHint )
self.setWindowFlag(
Qt.WindowType.FramelessWindowHint,
True,
) )
self.setAttribute( self.setAttribute(
@@ -273,32 +334,13 @@ class StickyNoteApp(AcrylicWindow):
True, True,
) )
self.setAutoFillBackground(False) self.setAttribute(
Qt.WidgetAttribute.WA_NoSystemBackground,
appearance = (
self.profile["appearance"]
)
try:
if appearance[
"enable_mica"
]:
self.windowEffect.setMicaEffect(
self.winId(),
True, True,
) )
elif appearance[ self.setAutoFillBackground(
"enable_acrylic" False
]:
self.windowEffect.setAcrylicEffect(
self.winId(),
"00000001",
)
except Exception:
logging.exception(
"window_effect_failure"
) )
def build_ui(self) -> None: def build_ui(self) -> None:
@@ -308,48 +350,34 @@ class StickyNoteApp(AcrylicWindow):
"container" "container"
) )
self.root.setGeometry(
self.rect()
)
self.layout = QVBoxLayout( self.layout = QVBoxLayout(
self.root self.root
) )
window = self.profile["window"]
margin = window[
"layout_margin"
]
self.layout.setContentsMargins( self.layout.setContentsMargins(
margin, 4,
margin, 4,
margin, 4,
margin, 4,
) )
self.layout.setSpacing( self.layout.setSpacing(0)
window["layout_spacing"]
) self.titleBar.hide()
self.titleBar.setFixedHeight(0)
self.drag_handle = DragHandle( self.drag_handle = DragHandle(
self.root self.root
) )
self.drag_handle.setFixedHeight( self.drag_handle.hide()
window[
"drag_handle_height"
]
)
self.drag_handle.drag_started.connect( self.drag_handle.setFixedHeight(0)
self.start_drag
)
self.drag_handle.drag_moved.connect(
self.perform_drag
)
self.drag_handle.drag_finished.connect(
self.finish_drag
)
self.editor = LinkTextEdit( self.editor = LinkTextEdit(
self.root self.root
@@ -367,6 +395,37 @@ class StickyNoteApp(AcrylicWindow):
0 0
) )
self.editor.setViewportMargins(
0,
0,
0,
0,
)
self.editor.setAcceptRichText(
False
)
self.editor.setMouseTracking(
True
)
self.editor.setStyleSheet("""
QTextEdit {
margin: 0px;
padding: 0px;
border: none;
background: transparent;
}
QTextEdit > QWidget {
margin: 0px;
padding: 0px;
border: none;
background: transparent;
}
""")
self.editor.textChanged.connect( self.editor.textChanged.connect(
self.queue_save self.queue_save
) )
@@ -396,13 +455,13 @@ class StickyNoteApp(AcrylicWindow):
) )
content_layout.setContentsMargins( content_layout.setContentsMargins(
0, 4,
0, 4,
0, 4,
0, 4,
) )
content_layout.setSpacing(0) content_layout.setSpacing(1)
content_layout.addWidget( content_layout.addWidget(
self.editor, self.editor,
@@ -418,10 +477,6 @@ class StickyNoteApp(AcrylicWindow):
self.root self.root
) )
self.layout.addWidget(
self.drag_handle
)
self.layout.addWidget( self.layout.addWidget(
content, content,
1, 1,
@@ -434,7 +489,12 @@ class StickyNoteApp(AcrylicWindow):
| Qt.AlignmentFlag.AlignRight, | Qt.AlignmentFlag.AlignRight,
) )
self.editor.setFocus()
def build_tray(self) -> None: def build_tray(self) -> None:
if not QSystemTrayIcon.isSystemTrayAvailable():
return
if not self.profile[ if not self.profile[
"behavior" "behavior"
]["enable_tray"]: ]["enable_tray"]:
@@ -474,7 +534,7 @@ class StickyNoteApp(AcrylicWindow):
) )
quit_action.triggered.connect( quit_action.triggered.connect(
self.close self.safe_exit
) )
menu.addAction(open_settings) menu.addAction(open_settings)
@@ -589,10 +649,46 @@ class StickyNoteApp(AcrylicWindow):
) )
) )
def start_drag( def initialize_effects(self) -> None:
self, appearance = (
global_pos: QPoint, self.profile["appearance"]
) -> None: )
try:
hwnd = int(self.winId())
if not hwnd:
return
if appearance[
"enable_mica"
]:
self.windowEffect.setMicaEffect(
hwnd,
True,
)
elif appearance[
"enable_acrylic"
]:
self.windowEffect.setAcrylicEffect(
hwnd,
"00000001",
)
self.update()
self.repaint()
except Exception:
logging.exception(
"window_effect_failure"
)
def activate_delayed_drag(self) -> None:
if not self.pending_drag:
return
self.window_state = ( self.window_state = (
WindowState.DRAGGING WindowState.DRAGGING
) )
@@ -603,27 +699,84 @@ class StickyNoteApp(AcrylicWindow):
self.apply_style() self.apply_style()
self.drag_offset = (
global_pos
- self.frameGeometry().topLeft()
)
self.setWindowOpacity( self.setWindowOpacity(
self.profile["appearance"][ self.profile["appearance"][
"opacity_dragging" "opacity_dragging"
] ]
) )
def perform_drag( def mousePressEvent(self, event) -> None:
if (
event.button()
== Qt.MouseButton.LeftButton
):
self.pending_drag = True
self.drag_origin = (
event.globalPosition()
.toPoint()
)
self.drag_offset = (
self.drag_origin
- self.frameGeometry().topLeft()
)
if hasattr(
self, self,
global_pos: QPoint, "drag_timer",
) -> None: ):
target = ( self.drag_timer.start(180)
super().mousePressEvent(event)
def mouseMoveEvent(self, event) -> None:
if not (
event.buttons()
& Qt.MouseButton.LeftButton
):
return
global_pos = (
event.globalPosition()
.toPoint()
)
if (
self.pending_drag
and (
global_pos
- self.drag_origin
).manhattanLength()
> 6
):
if (
self.window_state
== WindowState.DRAGGING
):
self.move(
global_pos global_pos
- self.drag_offset - self.drag_offset
) )
self.move(target) super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event) -> None:
if hasattr(
self,
"drag_timer",
):
self.drag_timer.stop()
self.pending_drag = False
if (
self.window_state
== WindowState.DRAGGING
):
self.finish_drag()
super().mouseReleaseEvent(event)
def finish_drag(self) -> None: def finish_drag(self) -> None:
self.window_state = ( self.window_state = (
@@ -642,7 +795,15 @@ class StickyNoteApp(AcrylicWindow):
] ]
) )
self.queue_save()
def queue_save(self) -> None: def queue_save(self) -> None:
if not hasattr(
self,
"save_timer",
):
return
self.save_timer.start( self.save_timer.start(
self.profile["editor"][ self.profile["editor"][
"save_debounce_ms" "save_debounce_ms"
@@ -650,6 +811,12 @@ class StickyNoteApp(AcrylicWindow):
) )
def queue_preview(self) -> None: def queue_preview(self) -> None:
if not hasattr(
self,
"preview_timer",
):
return
if not self.profile[ if not self.profile[
"editor" "editor"
][ ][
@@ -668,9 +835,16 @@ class StickyNoteApp(AcrylicWindow):
) )
def show_preview(self) -> None: def show_preview(self) -> None:
if self.editor.hasFocus(): if (
self.editor.hasFocus()
or not self.isActiveWindow()
):
return return
self.preview.setFocusPolicy(
Qt.FocusPolicy.NoFocus
)
markdown = ( markdown = (
self.editor.toPlainText() self.editor.toPlainText()
) )
@@ -698,6 +872,12 @@ class StickyNoteApp(AcrylicWindow):
self.preview.show() self.preview.show()
def save_state(self) -> None: def save_state(self) -> None:
try:
APP_DIR.mkdir(
parents=True,
exist_ok=True,
)
markdown = ( markdown = (
self.editor.toPlainText() self.editor.toPlainText()
) )
@@ -706,17 +886,21 @@ class StickyNoteApp(AcrylicWindow):
markdown.encode("utf-8") markdown.encode("utf-8")
).hexdigest() ).hexdigest()
if current_hash != self.last_saved_hash: if (
MARKDOWN_FILE.write_text( current_hash
!= self.last_saved_hash
):
atomic_write(
MARKDOWN_FILE,
markdown, markdown,
encoding="utf-8",
) )
self.last_saved_hash = ( self.last_saved_hash = (
current_hash current_hash
) )
STATE_FILE.write_text( atomic_write(
STATE_FILE,
json.dumps( json.dumps(
{ {
"x": self.x(), "x": self.x(),
@@ -726,21 +910,27 @@ class StickyNoteApp(AcrylicWindow):
}, },
indent=2, indent=2,
), ),
encoding="utf-8", )
except Exception:
logging.exception(
"save_state_failure"
) )
def restore_state(self) -> None: def restore_state(self) -> None:
if MARKDOWN_FILE.exists(): try:
self.editor.setPlainText( self.ensure_runtime_files()
markdown = (
MARKDOWN_FILE.read_text( MARKDOWN_FILE.read_text(
encoding="utf-8", encoding="utf-8",
) )
) )
if not STATE_FILE.exists(): self.editor.setPlainText(
return markdown
)
try:
state = json.loads( state = json.loads(
STATE_FILE.read_text( STATE_FILE.read_text(
encoding="utf-8", encoding="utf-8",
@@ -748,13 +938,25 @@ class StickyNoteApp(AcrylicWindow):
) )
self.resize( self.resize(
state["width"], max(
state["height"], state.get(
"width",
250,
),
200,
),
max(
state.get(
"height",
260,
),
100,
),
) )
self.move( self.move(
state["x"], state.get("x", 100),
state["y"], state.get("y", 100),
) )
self.clamp_to_screen() self.clamp_to_screen()
@@ -814,29 +1016,38 @@ class StickyNoteApp(AcrylicWindow):
self.apply_style() self.apply_style()
def open_settings(self) -> None: def open_settings(self) -> None:
path = ( SETTINGS_FILE.touch(
APP_DIR / "settings.json" exist_ok=True
) )
path.touch(exist_ok=True)
try: try:
if os.name == "nt": if os.name == "nt":
os.startfile(str(path)) os.startfile(
str(SETTINGS_FILE)
)
return return
if sys.platform.startswith( if sys.platform.startswith(
"linux" "linux"
): ):
subprocess.Popen( subprocess.Popen(
["xdg-open", str(path)] [
"xdg-open",
str(SETTINGS_FILE),
]
) )
return return
if sys.platform == "darwin": if sys.platform == "darwin":
subprocess.Popen( subprocess.Popen(
["open", str(path)] [
"open",
str(SETTINGS_FILE),
]
) )
return return
except Exception: except Exception:
@@ -844,26 +1055,21 @@ class StickyNoteApp(AcrylicWindow):
"open_settings_failure" "open_settings_failure"
) )
def nativeEvent( def moveEvent(self, event) -> None:
super().moveEvent(event)
if (
getattr(
self, self,
eventType, "startup_ready",
message,
):
if getattr(
self,
"_closing",
False, False,
):
return False, 0
try:
return super().nativeEvent(
eventType,
message,
) )
and hasattr(
except Exception: self,
return False, 0 "save_timer",
)
):
self.queue_save()
def resizeEvent(self, event) -> None: def resizeEvent(self, event) -> None:
super().resizeEvent(event) super().resizeEvent(event)
@@ -874,16 +1080,30 @@ class StickyNoteApp(AcrylicWindow):
None, None,
) )
if root is None: if root is not None:
return
geometry = self.rect() geometry = self.rect()
if root.geometry() != geometry: if (
root.geometry()
!= geometry
):
root.setGeometry( root.setGeometry(
geometry geometry
) )
if (
getattr(
self,
"startup_ready",
False,
)
and hasattr(
self,
"save_timer",
)
):
self.queue_save()
def focusInEvent(self, event) -> None: def focusInEvent(self, event) -> None:
super().focusInEvent(event) super().focusInEvent(event)
@@ -921,22 +1141,108 @@ class StickyNoteApp(AcrylicWindow):
) )
def closeEvent(self, event) -> None: def closeEvent(self, event) -> None:
self._closing = True if (
self._is_quitting
with contextlib.suppress(Exception): or self._force_close
):
try:
self.save_state() self.save_state()
except Exception:
traceback.print_exc()
with contextlib.suppress(Exception): event.accept()
return
event.ignore()
self.hide()
def register_shutdown_hooks(self) -> None:
app = QApplication.instance()
def handle_shutdown(*_) -> None:
self.safe_exit()
atexit.register(
handle_shutdown
)
for sig in (
signal.SIGINT,
signal.SIGTERM,
):
try:
signal.signal(
sig,
handle_shutdown,
)
except Exception:
pass
if app is not None:
app.aboutToQuit.connect(
lambda: setattr(
self,
"_is_quitting",
True,
)
)
def safe_exit(self) -> None:
if self._is_quitting:
return
self._is_quitting = True
try:
self.save_state()
except Exception:
traceback.print_exc()
try:
if hasattr(
self,
"save_timer",
):
self.save_timer.stop() self.save_timer.stop()
with contextlib.suppress(Exception): if hasattr(
self,
"preview_timer",
):
self.preview_timer.stop() self.preview_timer.stop()
with contextlib.suppress(Exception): except Exception:
self.opacity_anim.stop() traceback.print_exc()
with contextlib.suppress(Exception): try:
if hasattr(self, "tray"): if hasattr(
self,
"tray",
):
self.tray.hide() self.tray.hide()
super().closeEvent(event) self.tray.deleteLater()
except Exception:
traceback.print_exc()
try:
self.hide()
self.deleteLater()
except Exception:
traceback.print_exc()
app = QApplication.instance()
if app is not None:
app.quit()
try:
app.quit()
except Exception:
sys.exit(0)

View File

@@ -1,36 +1,65 @@
import os import os
import signal
import sys import sys
import traceback
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
from config.settings import (
APP_DIR,
)
from core.app import StickyNoteApp from core.app import StickyNoteApp
APP_DIR.mkdir(
parents=True,
exist_ok=True,
)
QGuiApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
)
def main() -> int: def main() -> int:
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" signal.signal(
signal.SIGINT,
signal.SIG_DFL,
)
QApplication.setQuitOnLastWindowClosed(False)
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setQuitLockEnabled(False) app.setQuitOnLastWindowClosed(
False
existing = getattr(app, "_sticky_note_instance", None) )
if existing is not None:
existing.raise_()
existing.activateWindow()
return 0
window = StickyNoteApp() window = StickyNoteApp()
app._sticky_note_instance = window
window.show() window.show()
try:
return app.exec() return app.exec()
except KeyboardInterrupt:
try:
window.safe_exit()
except Exception:
traceback.print_exc()
return 0
except SystemExit:
return 0
except Exception:
traceback.print_exc()
return 1
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())

BIN
ui/app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,10 +1,19 @@
# v3.0.0 editor.py # v4.1.0 editor-link-click-fix-v1
import re import re
import webbrowser import webbrowser
from PyQt6.QtCore import Qt, QUrl from PyQt6.QtCore import (
from PyQt6.QtGui import QDesktopServices Qt,
from PyQt6.QtWidgets import QTextEdit QUrl,
)
from PyQt6.QtGui import (
QDesktopServices,
)
from PyQt6.QtWidgets import (
QTextEdit,
)
URL_PATTERN = re.compile( URL_PATTERN = re.compile(
@@ -15,9 +24,14 @@ URL_PATTERN = re.compile(
class LinkTextEdit(QTextEdit): class LinkTextEdit(QTextEdit):
def mouseReleaseEvent(self, event) -> None: def mouseReleaseEvent(
self,
event,
) -> None:
if ( if (
event.modifiers() event.button()
== Qt.MouseButton.LeftButton
and event.modifiers()
& Qt.KeyboardModifier.ControlModifier & Qt.KeyboardModifier.ControlModifier
): ):
cursor = self.cursorForPosition( cursor = self.cursorForPosition(
@@ -47,14 +61,19 @@ class LinkTextEdit(QTextEdit):
) )
try: try:
webbrowser.open(target) webbrowser.open(
target
)
except Exception: except Exception:
QDesktopServices.openUrl( QDesktopServices.openUrl(
QUrl(target) QUrl(target)
) )
event.accept()
return return
super().mouseReleaseEvent(event) super().mouseReleaseEvent(
event
)

View File

@@ -28,7 +28,7 @@ def build_stylesheet(
QWidget#container {{ QWidget#container {{
background: {background}; background: {background};
border: 1px solid {border}; border: 1px solid {border};
border-radius: 16px; border-radius: 7px;
}} }}
QWidget#container:hover {{ QWidget#container:hover {{