plusui-native-core 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Core/CMakeLists.txt +190 -7
- package/Core/Features/App/app.cpp +129 -0
- package/Core/Features/App/app.ts +126 -0
- package/Core/Features/Browser/browser.cpp +181 -0
- package/Core/Features/Browser/browser.ts +182 -0
- package/Core/Features/Clipboard/clipboard.cpp +234 -0
- package/Core/Features/Clipboard/clipboard.ts +113 -0
- package/Core/Features/Display/display.cpp +209 -0
- package/Core/Features/Display/display.ts +104 -0
- package/Core/Features/Event/Events.ts +166 -0
- package/Core/Features/Event/events.cpp +200 -0
- package/Core/Features/Keyboard/keyboard.cpp +186 -0
- package/Core/Features/Keyboard/keyboard.ts +175 -0
- package/Core/Features/Menu/context-menu.css +293 -0
- package/Core/Features/Menu/menu.cpp +481 -0
- package/Core/Features/Menu/menu.ts +439 -0
- package/Core/Features/Tray/tray.cpp +310 -0
- package/Core/Features/Tray/tray.ts +68 -0
- package/Core/Features/WebGPU/webgpu.cpp +939 -0
- package/Core/Features/WebGPU/webgpu.ts +1013 -0
- package/Core/Features/WebView/webview.cpp +1052 -0
- package/Core/Features/WebView/webview.ts +510 -0
- package/Core/Features/Window/window.cpp +664 -0
- package/Core/Features/Window/window.ts +142 -0
- package/Core/Features/WindowManager/window_manager.cpp +341 -0
- package/Core/include/plusui/app.hpp +73 -0
- package/Core/include/plusui/browser.hpp +66 -0
- package/Core/include/plusui/clipboard.hpp +41 -0
- package/Core/include/plusui/events.hpp +58 -0
- package/Core/include/{keyboard.hpp → plusui/keyboard.hpp} +21 -44
- package/Core/include/plusui/menu.hpp +153 -0
- package/Core/include/plusui/tray.hpp +93 -0
- package/Core/include/plusui/webgpu.hpp +434 -0
- package/Core/include/plusui/webview.hpp +142 -0
- package/Core/include/plusui/window.hpp +111 -0
- package/Core/include/plusui/window_manager.hpp +57 -0
- package/Core/vendor/WebView2EnvironmentOptions.h +406 -0
- package/Core/vendor/stb_image.h +7988 -0
- package/Core/vendor/webview.h +618 -510
- package/Core/vendor/webview2.h +52079 -0
- package/README.md +19 -0
- package/package.json +12 -15
- package/Core/include/app.hpp +0 -121
- package/Core/include/menu.hpp +0 -79
- package/Core/include/tray.hpp +0 -81
- package/Core/include/window.hpp +0 -106
- package/Core/src/app.cpp +0 -311
- package/Core/src/display.cpp +0 -424
- package/Core/src/tray.cpp +0 -275
- package/Core/src/window.cpp +0 -528
- package/dist/index.d.ts +0 -205
- package/dist/index.js +0 -198
- package/src/index.ts +0 -574
- /package/Core/include/{display.hpp → plusui/display.hpp} +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#include <iostream>
|
|
2
|
+
#include <plusui/keyboard.hpp>
|
|
3
|
+
#include <unordered_map>
|
|
4
|
+
|
|
5
|
+
namespace plusui {
|
|
6
|
+
|
|
7
|
+
struct Keyboard::Impl {
|
|
8
|
+
std::unordered_map<int, bool> pressedKeys;
|
|
9
|
+
std::vector<std::function<void(const KeyEvent &)>> keyDownCallbacks;
|
|
10
|
+
std::vector<std::function<void(const KeyEvent &)>> keyUpCallbacks;
|
|
11
|
+
bool autoRepeat = true;
|
|
12
|
+
|
|
13
|
+
static const std::unordered_map<KeyCode, std::string> &keyNames() {
|
|
14
|
+
static std::unordered_map<KeyCode, std::string> names = {
|
|
15
|
+
{KeyCode::Space, "Space"},
|
|
16
|
+
{KeyCode::Escape, "Escape"},
|
|
17
|
+
{KeyCode::Enter, "Enter"},
|
|
18
|
+
{KeyCode::Tab, "Tab"},
|
|
19
|
+
{KeyCode::Backspace, "Backspace"},
|
|
20
|
+
{KeyCode::Delete, "Delete"},
|
|
21
|
+
{KeyCode::Right, "Right"},
|
|
22
|
+
{KeyCode::Left, "Left"},
|
|
23
|
+
{KeyCode::Down, "Down"},
|
|
24
|
+
{KeyCode::Up, "Up"},
|
|
25
|
+
{KeyCode::A, "A"},
|
|
26
|
+
{KeyCode::B, "B"},
|
|
27
|
+
{KeyCode::C, "C"},
|
|
28
|
+
{KeyCode::D, "D"},
|
|
29
|
+
{KeyCode::E, "E"},
|
|
30
|
+
{KeyCode::F, "F"},
|
|
31
|
+
{KeyCode::G, "G"},
|
|
32
|
+
{KeyCode::H, "H"},
|
|
33
|
+
{KeyCode::I, "I"},
|
|
34
|
+
{KeyCode::J, "J"},
|
|
35
|
+
{KeyCode::K, "K"},
|
|
36
|
+
{KeyCode::L, "L"},
|
|
37
|
+
{KeyCode::M, "M"},
|
|
38
|
+
{KeyCode::N, "N"},
|
|
39
|
+
{KeyCode::O, "O"},
|
|
40
|
+
{KeyCode::P, "P"},
|
|
41
|
+
{KeyCode::Q, "Q"},
|
|
42
|
+
{KeyCode::R, "R"},
|
|
43
|
+
{KeyCode::S, "S"},
|
|
44
|
+
{KeyCode::T, "T"},
|
|
45
|
+
{KeyCode::U, "U"},
|
|
46
|
+
{KeyCode::V, "V"},
|
|
47
|
+
{KeyCode::W, "W"},
|
|
48
|
+
{KeyCode::X, "X"},
|
|
49
|
+
{KeyCode::Y, "Y"},
|
|
50
|
+
{KeyCode::Z, "Z"},
|
|
51
|
+
{KeyCode::F1, "F1"},
|
|
52
|
+
{KeyCode::F2, "F2"},
|
|
53
|
+
{KeyCode::F3, "F3"},
|
|
54
|
+
{KeyCode::F4, "F4"},
|
|
55
|
+
{KeyCode::F5, "F5"},
|
|
56
|
+
{KeyCode::F6, "F6"},
|
|
57
|
+
{KeyCode::F7, "F7"},
|
|
58
|
+
{KeyCode::F8, "F8"},
|
|
59
|
+
{KeyCode::F9, "F9"},
|
|
60
|
+
{KeyCode::F10, "F10"},
|
|
61
|
+
{KeyCode::F11, "F11"},
|
|
62
|
+
{KeyCode::F12, "F12"},
|
|
63
|
+
};
|
|
64
|
+
return names;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
Keyboard::Keyboard() : pImpl(std::make_unique<Impl>()) {}
|
|
69
|
+
Keyboard::~Keyboard() = default;
|
|
70
|
+
|
|
71
|
+
Keyboard &Keyboard::instance() {
|
|
72
|
+
static Keyboard instance;
|
|
73
|
+
return instance;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void Keyboard::onKeyDown(std::function<void(const KeyEvent &)> callback) {
|
|
77
|
+
pImpl->keyDownCallbacks.push_back(callback);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void Keyboard::onKeyUp(std::function<void(const KeyEvent &)> callback) {
|
|
81
|
+
pImpl->keyUpCallbacks.push_back(callback);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
bool Keyboard::isKeyPressed(KeyCode key) const {
|
|
85
|
+
return pImpl->pressedKeys.count(static_cast<int>(key)) &&
|
|
86
|
+
pImpl->pressedKeys.at(static_cast<int>(key));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
bool Keyboard::areModsActive(KeyMod mods) const { return false; }
|
|
90
|
+
|
|
91
|
+
std::string Keyboard::getKeyName(KeyCode key) const {
|
|
92
|
+
auto it = Impl::keyNames().find(key);
|
|
93
|
+
return it != Impl::keyNames().end() ? it->second : "Unknown";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
KeyCode Keyboard::getKeyFromName(const std::string &name) const {
|
|
97
|
+
for (const auto &[key, keyName] : Impl::keyNames()) {
|
|
98
|
+
if (keyName == name)
|
|
99
|
+
return key;
|
|
100
|
+
}
|
|
101
|
+
return KeyCode::Unknown;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void Keyboard::setAutoRepeat(bool enabled) { pImpl->autoRepeat = enabled; }
|
|
105
|
+
|
|
106
|
+
bool Keyboard::getAutoRepeat() const { return pImpl->autoRepeat; }
|
|
107
|
+
|
|
108
|
+
struct ShortcutManager::Impl {
|
|
109
|
+
std::map<std::string, std::pair<Shortcut, std::function<void()>>> shortcuts;
|
|
110
|
+
std::function<void(const std::string &id)> triggerCallback;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
ShortcutManager::ShortcutManager() : pImpl(std::make_unique<Impl>()) {}
|
|
114
|
+
ShortcutManager::~ShortcutManager() = default;
|
|
115
|
+
|
|
116
|
+
ShortcutManager &ShortcutManager::instance() {
|
|
117
|
+
static ShortcutManager instance;
|
|
118
|
+
return instance;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
bool ShortcutManager::registerShortcut(const std::string &id,
|
|
122
|
+
const Shortcut &shortcut,
|
|
123
|
+
std::function<void()> callback) {
|
|
124
|
+
pImpl->shortcuts[id] = {shortcut, callback};
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
bool ShortcutManager::unregisterShortcut(const std::string &id) {
|
|
129
|
+
return pImpl->shortcuts.erase(id) > 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void ShortcutManager::clearShortcuts() { pImpl->shortcuts.clear(); }
|
|
133
|
+
|
|
134
|
+
bool ShortcutManager::isRegistered(const std::string &id) const {
|
|
135
|
+
return pImpl->shortcuts.count(id) > 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Shortcut ShortcutManager::getShortcut(const std::string &id) const {
|
|
139
|
+
auto it = pImpl->shortcuts.find(id);
|
|
140
|
+
return it != pImpl->shortcuts.end() ? it->second.first : Shortcut();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
void ShortcutManager::onShortcutTriggered(
|
|
144
|
+
std::function<void(const std::string &id)> callback) {
|
|
145
|
+
pImpl->triggerCallback = callback;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
std::string Shortcut::toString() const {
|
|
149
|
+
std::string s;
|
|
150
|
+
if (mods & KeyMod::Control)
|
|
151
|
+
s += "Ctrl+";
|
|
152
|
+
if (mods & KeyMod::Alt)
|
|
153
|
+
s += "Alt+";
|
|
154
|
+
if (mods & KeyMod::Shift)
|
|
155
|
+
s += "Shift+";
|
|
156
|
+
if (mods & KeyMod::Super)
|
|
157
|
+
s += "Super+";
|
|
158
|
+
return s + std::to_string(static_cast<int>(key));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
Shortcut Shortcut::parse(const std::string &str) {
|
|
162
|
+
Shortcut shortcut;
|
|
163
|
+
KeyMod mods = KeyMod::None;
|
|
164
|
+
std::string key;
|
|
165
|
+
|
|
166
|
+
for (size_t i = 0; i < str.size(); ++i) {
|
|
167
|
+
if (str[i] == '+') {
|
|
168
|
+
if (key == "Ctrl" || key == "Control")
|
|
169
|
+
mods = mods | KeyMod::Control;
|
|
170
|
+
else if (key == "Alt")
|
|
171
|
+
mods = mods | KeyMod::Alt;
|
|
172
|
+
else if (key == "Shift")
|
|
173
|
+
mods = mods | KeyMod::Shift;
|
|
174
|
+
else if (key == "Super" || key == "Win")
|
|
175
|
+
mods = mods | KeyMod::Super;
|
|
176
|
+
key.clear();
|
|
177
|
+
} else {
|
|
178
|
+
key += str[i];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
shortcut.mods = mods;
|
|
183
|
+
return shortcut;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
} // namespace plusui
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export enum KeyCode {
|
|
2
|
+
Unknown = 0,
|
|
3
|
+
Space = 32,
|
|
4
|
+
Escape = 256,
|
|
5
|
+
Enter = 257,
|
|
6
|
+
Tab = 258,
|
|
7
|
+
Backspace = 259,
|
|
8
|
+
Delete = 261,
|
|
9
|
+
Right = 262,
|
|
10
|
+
Left = 263,
|
|
11
|
+
Down = 264,
|
|
12
|
+
Up = 265,
|
|
13
|
+
F1 = 290,
|
|
14
|
+
F2 = 291,
|
|
15
|
+
F3 = 292,
|
|
16
|
+
F4 = 293,
|
|
17
|
+
F5 = 294,
|
|
18
|
+
F6 = 295,
|
|
19
|
+
F7 = 296,
|
|
20
|
+
F8 = 297,
|
|
21
|
+
F9 = 298,
|
|
22
|
+
F10 = 299,
|
|
23
|
+
F11 = 300,
|
|
24
|
+
F12 = 301,
|
|
25
|
+
LeftShift = 340,
|
|
26
|
+
LeftControl = 341,
|
|
27
|
+
LeftAlt = 342,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export enum KeyMod {
|
|
31
|
+
None = 0,
|
|
32
|
+
Shift = 1,
|
|
33
|
+
Control = 2,
|
|
34
|
+
Alt = 4,
|
|
35
|
+
Super = 8,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface KeyEvent {
|
|
39
|
+
key: KeyCode;
|
|
40
|
+
scancode: number;
|
|
41
|
+
mods: KeyMod;
|
|
42
|
+
pressed: boolean;
|
|
43
|
+
repeat: boolean;
|
|
44
|
+
keyName: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Shortcut {
|
|
48
|
+
key: KeyCode;
|
|
49
|
+
mods: KeyMod;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type KeyEventCallback = (event: KeyEvent) => void;
|
|
53
|
+
export type ShortcutCallback = () => void;
|
|
54
|
+
|
|
55
|
+
export class KeyboardAPI {
|
|
56
|
+
private shortcutHandlers: Map<string, ShortcutCallback> = new Map();
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
private invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
|
|
60
|
+
private eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
|
|
61
|
+
) {
|
|
62
|
+
this.setupEventListeners();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private setupEventListeners(): void {
|
|
66
|
+
this.eventFn('keyboard:keydown', (data) => {
|
|
67
|
+
const event = data as KeyEvent;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.eventFn('keyboard:keyup', (data) => {
|
|
71
|
+
const event = data as KeyEvent;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.eventFn('keyboard:shortcut', (data) => {
|
|
75
|
+
const { id } = data as { id: string };
|
|
76
|
+
const handler = this.shortcutHandlers.get(id);
|
|
77
|
+
if (handler) handler();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async isKeyPressed(key: KeyCode): Promise<boolean> {
|
|
82
|
+
return await this.invokeFn<boolean>('keyboard.isKeyPressed', [key]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async setAutoRepeat(enabled: boolean): Promise<void> {
|
|
86
|
+
await this.invokeFn('keyboard.setAutoRepeat', [enabled]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getAutoRepeat(): Promise<boolean> {
|
|
90
|
+
return await this.invokeFn<boolean>('keyboard.getAutoRepeat', []);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onKeyDown(callback: KeyEventCallback): () => void {
|
|
94
|
+
return this.eventFn('keyboard:keydown', callback as (...args: unknown[]) => void);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onKeyUp(callback: KeyEventCallback): () => void {
|
|
98
|
+
return this.eventFn('keyboard:keyup', callback as (...args: unknown[]) => void);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async registerShortcut(id: string, shortcut: Shortcut, callback: ShortcutCallback): Promise<boolean> {
|
|
102
|
+
this.shortcutHandlers.set(id, callback);
|
|
103
|
+
return await this.invokeFn<boolean>('keyboard.registerShortcut', [id, shortcut]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async unregisterShortcut(id: string): Promise<boolean> {
|
|
107
|
+
this.shortcutHandlers.delete(id);
|
|
108
|
+
return await this.invokeFn<boolean>('keyboard.unregisterShortcut', [id]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async clearShortcuts(): Promise<void> {
|
|
112
|
+
this.shortcutHandlers.clear();
|
|
113
|
+
await this.invokeFn('keyboard.clearShortcuts', []);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
parseShortcut(str: string): Shortcut {
|
|
117
|
+
const parts = str.toLowerCase().split('+');
|
|
118
|
+
let mods = KeyMod.None;
|
|
119
|
+
let key = KeyCode.Unknown;
|
|
120
|
+
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
const trimmed = part.trim();
|
|
123
|
+
if (trimmed === 'ctrl' || trimmed === 'control') {
|
|
124
|
+
mods |= KeyMod.Control;
|
|
125
|
+
} else if (trimmed === 'alt') {
|
|
126
|
+
mods |= KeyMod.Alt;
|
|
127
|
+
} else if (trimmed === 'shift') {
|
|
128
|
+
mods |= KeyMod.Shift;
|
|
129
|
+
} else if (trimmed === 'super' || trimmed === 'win' || trimmed === 'cmd') {
|
|
130
|
+
mods |= KeyMod.Super;
|
|
131
|
+
} else {
|
|
132
|
+
key = this.keyNameToCode(trimmed);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { key, mods };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private keyNameToCode(name: string): KeyCode {
|
|
140
|
+
const map: Record<string, KeyCode> = {
|
|
141
|
+
'space': KeyCode.Space,
|
|
142
|
+
'escape': KeyCode.Escape,
|
|
143
|
+
'enter': KeyCode.Enter,
|
|
144
|
+
'tab': KeyCode.Tab,
|
|
145
|
+
'backspace': KeyCode.Backspace,
|
|
146
|
+
'delete': KeyCode.Delete,
|
|
147
|
+
'right': KeyCode.Right,
|
|
148
|
+
'left': KeyCode.Left,
|
|
149
|
+
'down': KeyCode.Down,
|
|
150
|
+
'up': KeyCode.Up,
|
|
151
|
+
'f1': KeyCode.F1,
|
|
152
|
+
'f2': KeyCode.F2,
|
|
153
|
+
'f3': KeyCode.F3,
|
|
154
|
+
'f4': KeyCode.F4,
|
|
155
|
+
'f5': KeyCode.F5,
|
|
156
|
+
'f6': KeyCode.F6,
|
|
157
|
+
'f7': KeyCode.F7,
|
|
158
|
+
'f8': KeyCode.F8,
|
|
159
|
+
'f9': KeyCode.F9,
|
|
160
|
+
'f10': KeyCode.F10,
|
|
161
|
+
'f11': KeyCode.F11,
|
|
162
|
+
'f12': KeyCode.F12,
|
|
163
|
+
};
|
|
164
|
+
return map[name] || KeyCode.Unknown;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function createKeyboardAPI(
|
|
169
|
+
invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
|
|
170
|
+
eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
|
|
171
|
+
): KeyboardAPI {
|
|
172
|
+
return new KeyboardAPI(invokeFn, eventFn);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default KeyboardAPI;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlusUI Context Menu Styles
|
|
3
|
+
*
|
|
4
|
+
* These styles are for HTML-based context menus rendered in the webview.
|
|
5
|
+
* Native OS menus (Windows, macOS, Linux) use system styles automatically.
|
|
6
|
+
*
|
|
7
|
+
* Use these when you want a custom-styled context menu that matches your app's theme.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* ============================================================================
|
|
11
|
+
Base Context Menu Container
|
|
12
|
+
============================================================================ */
|
|
13
|
+
|
|
14
|
+
.plusui-context-menu {
|
|
15
|
+
position: fixed;
|
|
16
|
+
z-index: 10000;
|
|
17
|
+
min-width: 180px;
|
|
18
|
+
max-width: 300px;
|
|
19
|
+
padding: 4px 0;
|
|
20
|
+
background: var(--menu-bg, #ffffff);
|
|
21
|
+
border: 1px solid var(--menu-border, #e0e0e0);
|
|
22
|
+
border-radius: 8px;
|
|
23
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15),
|
|
24
|
+
0 1px 3px rgba(0, 0, 0, 0.1);
|
|
25
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
26
|
+
font-size: 13px;
|
|
27
|
+
user-select: none;
|
|
28
|
+
animation: plusui-menu-fade-in 0.15s ease-out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes plusui-menu-fade-in {
|
|
32
|
+
from {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
transform: scale(0.95);
|
|
35
|
+
}
|
|
36
|
+
to {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
transform: scale(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ============================================================================
|
|
43
|
+
Menu Items
|
|
44
|
+
============================================================================ */
|
|
45
|
+
|
|
46
|
+
.plusui-context-menu-item {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
padding: 8px 12px;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
color: var(--menu-text, #333333);
|
|
52
|
+
transition: background-color 0.1s ease;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.plusui-context-menu-item:hover {
|
|
56
|
+
background-color: var(--menu-hover, #f0f0f0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.plusui-context-menu-item:active {
|
|
60
|
+
background-color: var(--menu-active, #e0e0e0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.plusui-context-menu-item.disabled {
|
|
64
|
+
color: var(--menu-disabled, #999999);
|
|
65
|
+
cursor: not-allowed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.plusui-context-menu-item.disabled:hover {
|
|
69
|
+
background-color: transparent;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ============================================================================
|
|
73
|
+
Menu Item Components
|
|
74
|
+
============================================================================ */
|
|
75
|
+
|
|
76
|
+
.plusui-context-menu-icon {
|
|
77
|
+
width: 16px;
|
|
78
|
+
height: 16px;
|
|
79
|
+
margin-right: 10px;
|
|
80
|
+
flex-shrink: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.plusui-context-menu-label {
|
|
84
|
+
flex: 1;
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
text-overflow: ellipsis;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.plusui-context-menu-accelerator {
|
|
91
|
+
margin-left: 24px;
|
|
92
|
+
color: var(--menu-accelerator, #888888);
|
|
93
|
+
font-size: 12px;
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.plusui-context-menu-arrow {
|
|
98
|
+
margin-left: 8px;
|
|
99
|
+
color: var(--menu-arrow, #666666);
|
|
100
|
+
font-size: 10px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ============================================================================
|
|
104
|
+
Separators
|
|
105
|
+
============================================================================ */
|
|
106
|
+
|
|
107
|
+
.plusui-context-menu-separator {
|
|
108
|
+
height: 1px;
|
|
109
|
+
margin: 4px 12px;
|
|
110
|
+
background-color: var(--menu-separator, #e0e0e0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ============================================================================
|
|
114
|
+
Checkbox & Radio Items
|
|
115
|
+
============================================================================ */
|
|
116
|
+
|
|
117
|
+
.plusui-context-menu-item.checkbox::before,
|
|
118
|
+
.plusui-context-menu-item.radio::before {
|
|
119
|
+
content: '';
|
|
120
|
+
width: 16px;
|
|
121
|
+
height: 16px;
|
|
122
|
+
margin-right: 10px;
|
|
123
|
+
border: 2px solid var(--menu-checkbox-border, #666666);
|
|
124
|
+
border-radius: 3px;
|
|
125
|
+
flex-shrink: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.plusui-context-menu-item.radio::before {
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.plusui-context-menu-item.checkbox.checked::before {
|
|
133
|
+
background-color: var(--menu-checkbox-checked, #0066cc);
|
|
134
|
+
border-color: var(--menu-checkbox-checked, #0066cc);
|
|
135
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='white' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E");
|
|
136
|
+
background-size: 12px;
|
|
137
|
+
background-position: center;
|
|
138
|
+
background-repeat: no-repeat;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.plusui-context-menu-item.radio.checked::before {
|
|
142
|
+
background-color: var(--menu-radio-checked, #0066cc);
|
|
143
|
+
border-color: var(--menu-radio-checked, #0066cc);
|
|
144
|
+
box-shadow: inset 0 0 0 3px var(--menu-bg, #ffffff);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ============================================================================
|
|
148
|
+
Submenus
|
|
149
|
+
============================================================================ */
|
|
150
|
+
|
|
151
|
+
.plusui-context-menu-submenu {
|
|
152
|
+
position: relative;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.plusui-context-menu-submenu > .plusui-context-menu {
|
|
156
|
+
position: absolute;
|
|
157
|
+
top: -4px;
|
|
158
|
+
left: 100%;
|
|
159
|
+
margin-left: 2px;
|
|
160
|
+
display: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.plusui-context-menu-submenu:hover > .plusui-context-menu {
|
|
164
|
+
display: block;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* ============================================================================
|
|
168
|
+
Dark Theme
|
|
169
|
+
============================================================================ */
|
|
170
|
+
|
|
171
|
+
.plusui-context-menu.dark,
|
|
172
|
+
[data-theme="dark"] .plusui-context-menu {
|
|
173
|
+
--menu-bg: #2d2d2d;
|
|
174
|
+
--menu-border: #404040;
|
|
175
|
+
--menu-text: #e0e0e0;
|
|
176
|
+
--menu-hover: #3d3d3d;
|
|
177
|
+
--menu-active: #4d4d4d;
|
|
178
|
+
--menu-disabled: #666666;
|
|
179
|
+
--menu-separator: #404040;
|
|
180
|
+
--menu-accelerator: #888888;
|
|
181
|
+
--menu-arrow: #888888;
|
|
182
|
+
--menu-checkbox-border: #888888;
|
|
183
|
+
--menu-checkbox-checked: #4da6ff;
|
|
184
|
+
--menu-radio-checked: #4da6ff;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ============================================================================
|
|
188
|
+
macOS Style
|
|
189
|
+
============================================================================ */
|
|
190
|
+
|
|
191
|
+
.plusui-context-menu.macos {
|
|
192
|
+
border-radius: 6px;
|
|
193
|
+
padding: 3px 0;
|
|
194
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2),
|
|
195
|
+
0 0 1px rgba(0, 0, 0, 0.1);
|
|
196
|
+
font-size: 13px;
|
|
197
|
+
-webkit-backdrop-filter: blur(20px);
|
|
198
|
+
backdrop-filter: blur(20px);
|
|
199
|
+
background: rgba(255, 255, 255, 0.85);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.plusui-context-menu.macos .plusui-context-menu-item {
|
|
203
|
+
padding: 4px 12px;
|
|
204
|
+
border-radius: 4px;
|
|
205
|
+
margin: 0 4px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.plusui-context-menu.macos .plusui-context-menu-item:hover {
|
|
209
|
+
background-color: #0066cc;
|
|
210
|
+
color: white;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.plusui-context-menu.macos .plusui-context-menu-item:hover .plusui-context-menu-accelerator {
|
|
214
|
+
color: rgba(255, 255, 255, 0.7);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ============================================================================
|
|
218
|
+
Windows 11 Style
|
|
219
|
+
============================================================================ */
|
|
220
|
+
|
|
221
|
+
.plusui-context-menu.windows11 {
|
|
222
|
+
border-radius: 8px;
|
|
223
|
+
padding: 4px;
|
|
224
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
|
225
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
226
|
+
background: rgba(255, 255, 255, 0.95);
|
|
227
|
+
-webkit-backdrop-filter: blur(50px) saturate(150%);
|
|
228
|
+
backdrop-filter: blur(50px) saturate(150%);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.plusui-context-menu.windows11 .plusui-context-menu-item {
|
|
232
|
+
padding: 10px 12px;
|
|
233
|
+
border-radius: 4px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.plusui-context-menu.windows11 .plusui-context-menu-item:hover {
|
|
237
|
+
background-color: rgba(0, 0, 0, 0.04);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.plusui-context-menu.windows11 .plusui-context-menu-separator {
|
|
241
|
+
margin: 4px 8px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* ============================================================================
|
|
245
|
+
Fluent Design (Acrylic)
|
|
246
|
+
============================================================================ */
|
|
247
|
+
|
|
248
|
+
.plusui-context-menu.fluent {
|
|
249
|
+
background: rgba(255, 255, 255, 0.7);
|
|
250
|
+
-webkit-backdrop-filter: blur(40px) saturate(180%);
|
|
251
|
+
backdrop-filter: blur(40px) saturate(180%);
|
|
252
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
253
|
+
border-radius: 8px;
|
|
254
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* ============================================================================
|
|
258
|
+
Compact Style (for toolbars, etc.)
|
|
259
|
+
============================================================================ */
|
|
260
|
+
|
|
261
|
+
.plusui-context-menu.compact {
|
|
262
|
+
min-width: 120px;
|
|
263
|
+
font-size: 12px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.plusui-context-menu.compact .plusui-context-menu-item {
|
|
267
|
+
padding: 6px 10px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.plusui-context-menu.compact .plusui-context-menu-icon {
|
|
271
|
+
width: 14px;
|
|
272
|
+
height: 14px;
|
|
273
|
+
margin-right: 8px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* ============================================================================
|
|
277
|
+
Utility Classes
|
|
278
|
+
============================================================================ */
|
|
279
|
+
|
|
280
|
+
/* Position menu above cursor instead of below */
|
|
281
|
+
.plusui-context-menu.above {
|
|
282
|
+
transform-origin: bottom left;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Right-aligned menu */
|
|
286
|
+
.plusui-context-menu.right-aligned {
|
|
287
|
+
transform-origin: top right;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* No animation */
|
|
291
|
+
.plusui-context-menu.no-animation {
|
|
292
|
+
animation: none;
|
|
293
|
+
}
|