[refactor] reorganize platform and wire sources
@@ -0,0 +1,458 @@
|
||||
// The legacy client uses ImGui::StyleColorsLight(). Keep the structural line
|
||||
// colors in one place so the Slint renderer produces the same visual weight.
|
||||
export global ImGuiLineStyle {
|
||||
out property <color> border: #0000004d;
|
||||
out property <color> separator: #6363639e;
|
||||
out property <color> divider: #0000007a;
|
||||
out property <color> control-separator: #b2b2b2;
|
||||
}
|
||||
|
||||
// ImGui loads a 32px font atlas and applies per-window font scales. These
|
||||
// logical sizes reproduce those visual tiers on Slint's HiDPI renderer.
|
||||
export global ImGuiFontStyle {
|
||||
out property <length> base: 12px;
|
||||
out property <length> section-title: 18px;
|
||||
out property <length> panel-label: 17px;
|
||||
out property <length> field-value: 22px;
|
||||
out property <length> prominent: 16px;
|
||||
out property <length> body: 11px;
|
||||
out property <length> small: 10px;
|
||||
}
|
||||
|
||||
// Font Awesome 6 Free Solid glyphs used by the legacy ImGui client. The font
|
||||
// data is registered for every Slint window by GuiApplication before show().
|
||||
export global FontAwesomeIcons {
|
||||
out property <string> angle-down: "\u{f107}";
|
||||
out property <string> angle-left: "\u{f104}";
|
||||
out property <string> angle-right: "\u{f105}";
|
||||
out property <string> angle-up: "\u{f106}";
|
||||
out property <string> arrow-right-long: "\u{f178}";
|
||||
out property <string> bars: "\u{f0c9}";
|
||||
out property <string> check: "\u{f00c}";
|
||||
out property <string> circle-arrow-up: "\u{f0aa}";
|
||||
out property <string> compress: "\u{f066}";
|
||||
out property <string> computer-mouse: "\u{f8cc}";
|
||||
out property <string> copy: "\u{f0c5}";
|
||||
out property <string> display: "\u{e163}";
|
||||
out property <string> expand: "\u{f065}";
|
||||
out property <string> eye: "\u{f06e}";
|
||||
out property <string> eye-slash: "\u{f070}";
|
||||
out property <string> folder-open: "\u{f07c}";
|
||||
out property <string> keyboard: "\u{f11c}";
|
||||
out property <string> minus: "\u{f068}";
|
||||
out property <string> pen: "\u{f304}";
|
||||
out property <string> signal: "\u{f012}";
|
||||
out property <string> shield-halved: "\u{f3ed}";
|
||||
out property <string> square-full: "\u{f45c}";
|
||||
out property <string> trash-can: "\u{f2ed}";
|
||||
out property <string> volume-high: "\u{f028}";
|
||||
out property <string> volume-xmark: "\u{f6a9}";
|
||||
out property <string> xmark: "\u{f00d}";
|
||||
}
|
||||
|
||||
export component IconButton inherits Rectangle {
|
||||
in property <string> icon;
|
||||
in property <string> tooltip;
|
||||
in property <bool> danger: false;
|
||||
in property <length> icon-size: 16px;
|
||||
in property <color> icon-color: #24272d;
|
||||
in property <color> normal-background: transparent;
|
||||
in property <color> hover-background: #edf0f5;
|
||||
in property <color> pressed-background: #d9dde5;
|
||||
in property <bool> slashed: false;
|
||||
callback clicked;
|
||||
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: touch.pressed ? (danger ? #d92d20 : root.pressed-background)
|
||||
: touch.has-hover ? (danger ? #f04438 : root.hover-background)
|
||||
: root.normal-background;
|
||||
border-radius: 4px;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
color: touch.has-hover && root.danger ? white : root.icon-color;
|
||||
font-family: "Font Awesome 6 Free";
|
||||
font-weight: 900;
|
||||
font-size: root.icon-size;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
if root.slashed: Text {
|
||||
text: "/";
|
||||
color: touch.has-hover && root.danger ? white : root.icon-color;
|
||||
font-size: root.icon-size + 7px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
}
|
||||
|
||||
if touch.has-hover && root.tooltip != "": Rectangle {
|
||||
x: min(root.width - self.width, 0px);
|
||||
y: root.height + 3px;
|
||||
width: tip.preferred-width + 12px;
|
||||
height: 24px;
|
||||
background: #22252be8;
|
||||
border-radius: 4px;
|
||||
z: 20;
|
||||
tip := Text {
|
||||
text: root.tooltip;
|
||||
color: white;
|
||||
font-size: ImGuiFontStyle.small;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retained for non-font artwork. UI action icons use IconButton so the Slint
|
||||
// and ImGui clients share Font Awesome's glyph geometry.
|
||||
export component ImageButton inherits Rectangle {
|
||||
in property <image> icon;
|
||||
in property <string> tooltip;
|
||||
in property <bool> danger: false;
|
||||
in property <length> icon-width: 16px;
|
||||
in property <length> icon-height: 16px;
|
||||
callback clicked;
|
||||
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: touch.pressed ? (danger ? #d92d20 : #d9dde5)
|
||||
: touch.has-hover ? (danger ? #f04438 : #edf0f5)
|
||||
: transparent;
|
||||
border-radius: 4px;
|
||||
|
||||
Image {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: root.icon-width;
|
||||
height: root.icon-height;
|
||||
source: root.icon;
|
||||
image-fit: contain;
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
}
|
||||
|
||||
if touch.has-hover && root.tooltip != "": Rectangle {
|
||||
x: min(root.width - self.width, 0px);
|
||||
y: root.height + 3px;
|
||||
width: tip.preferred-width + 12px;
|
||||
height: 24px;
|
||||
background: #22252be8;
|
||||
border-radius: 4px;
|
||||
z: 20;
|
||||
tip := Text {
|
||||
text: root.tooltip;
|
||||
color: white;
|
||||
font-size: ImGuiFontStyle.small;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component CompactComboBox inherits Rectangle {
|
||||
in property <[string]> model;
|
||||
in-out property <int> current-index: 0;
|
||||
in property <bool> enabled: true;
|
||||
property <bool> popup-animation-enabled: false;
|
||||
property <float> popup-progress: 0.0;
|
||||
callback selected(int);
|
||||
|
||||
animate popup-progress {
|
||||
duration: 160ms;
|
||||
easing: ease-out;
|
||||
enabled: root.popup-animation-enabled;
|
||||
}
|
||||
|
||||
width: 73px;
|
||||
height: 24px;
|
||||
background: root.enabled ? white : #f1f1f1;
|
||||
border-width: 1px;
|
||||
border-color: #bfc2c7;
|
||||
border-radius: 3px;
|
||||
|
||||
Text {
|
||||
x: 5px;
|
||||
width: parent.width - 24px;
|
||||
text: root.model[root.current-index];
|
||||
color: root.enabled ? #202124 : #8b8f95;
|
||||
font-size: ImGuiFontStyle.body;
|
||||
overflow: elide;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
Rectangle {
|
||||
x: parent.width - 21px;
|
||||
width: 20px;
|
||||
height: parent.height - 2px;
|
||||
y: 1px;
|
||||
background: root.enabled ? #9bc9f8 : #d7d9dc;
|
||||
Text {
|
||||
text: FontAwesomeIcons.angle-down;
|
||||
color: #202124;
|
||||
font-family: "Font Awesome 6 Free";
|
||||
font-weight: 900;
|
||||
font-size: 10px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
transform-rotation: combo-popup.is-open ? 180deg : 0deg;
|
||||
animate transform-rotation {
|
||||
duration: 140ms;
|
||||
easing: ease-out;
|
||||
enabled: combo-popup.is-open;
|
||||
}
|
||||
}
|
||||
}
|
||||
TouchArea {
|
||||
enabled: root.enabled;
|
||||
clicked => {
|
||||
root.popup-animation-enabled = false;
|
||||
root.popup-progress = 0.0;
|
||||
combo-popup.show();
|
||||
popup-open-timer.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Start on the next frame so the native popup first renders at zero
|
||||
// height, making the downward expansion visible on every backend.
|
||||
popup-open-timer := Timer {
|
||||
interval: 16ms;
|
||||
running: false;
|
||||
triggered => {
|
||||
self.running = false;
|
||||
if combo-popup.is-open {
|
||||
root.popup-animation-enabled = true;
|
||||
root.popup-progress = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combo-popup := PopupWindow {
|
||||
x: 0px;
|
||||
y: root.height;
|
||||
width: root.width;
|
||||
height: root.model.length * 23px;
|
||||
close-policy: close-on-click-outside;
|
||||
|
||||
Rectangle {
|
||||
// Keep the reveal anchored to the popup's top edge while the
|
||||
// content settles downward into its final position.
|
||||
y: (1.0 - root.popup-progress) * -8px;
|
||||
width: parent.width;
|
||||
height: parent.height * root.popup-progress;
|
||||
opacity: root.popup-progress;
|
||||
background: white;
|
||||
border-width: 1px;
|
||||
border-color: #bfc2c7;
|
||||
border-radius: 3px;
|
||||
clip: true;
|
||||
for choice[index] in root.model: Rectangle {
|
||||
y: index * 23px;
|
||||
width: parent.width;
|
||||
height: 23px;
|
||||
background: option-touch.has-hover ? #dbeafb : index == root.current-index ? #eef5fd : white;
|
||||
Text { x: 5px; width: parent.width - 10px; text: choice; color: #202124; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
||||
option-touch := TouchArea {
|
||||
clicked => {
|
||||
popup-open-timer.running = false;
|
||||
root.popup-animation-enabled = false;
|
||||
root.popup-progress = 0.0;
|
||||
root.current-index = index;
|
||||
root.selected(index);
|
||||
combo-popup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
background: transparent;
|
||||
border-width: 1px;
|
||||
border-color: #bfc2c7;
|
||||
border-radius: 3px;
|
||||
z: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ImGuiCheckBox inherits Rectangle {
|
||||
in-out property <bool> checked;
|
||||
in property <bool> enabled: true;
|
||||
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background: root.enabled ? white : #f1f1f1;
|
||||
border-width: 1px;
|
||||
border-color: check-touch.has-hover && root.enabled ? #8cbef2 : #b9bdc2;
|
||||
border-radius: 4px;
|
||||
if root.checked: Text {
|
||||
text: FontAwesomeIcons.check;
|
||||
color: root.enabled ? #2e86de : #8b8f95;
|
||||
font-family: "Font Awesome 6 Free";
|
||||
font-weight: 900;
|
||||
font-size: 16px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
check-touch := TouchArea {
|
||||
enabled: root.enabled;
|
||||
clicked => { root.checked = !root.checked; }
|
||||
}
|
||||
}
|
||||
|
||||
export component MiniToggleSwitch inherits Rectangle {
|
||||
in-out property <bool> checked;
|
||||
in property <bool> enabled: true;
|
||||
callback toggled(bool);
|
||||
|
||||
width: 29px;
|
||||
height: 16px;
|
||||
background: !root.enabled ? (root.checked ? #6e9ddacc : #9a9a9acc)
|
||||
: root.checked ? #2463c7 : #9a9a9a;
|
||||
border-radius: 8px;
|
||||
Rectangle {
|
||||
x: root.checked ? parent.width - self.width - 2px : 2px;
|
||||
y: 2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
background: white;
|
||||
}
|
||||
TouchArea {
|
||||
enabled: root.enabled;
|
||||
clicked => { root.checked = !root.checked; root.toggled(root.checked); }
|
||||
}
|
||||
}
|
||||
|
||||
export component CompactButton inherits Rectangle {
|
||||
in property <string> text;
|
||||
in property <bool> primary: false;
|
||||
in property <bool> enabled: true;
|
||||
callback clicked;
|
||||
|
||||
width: 34px;
|
||||
height: 24px;
|
||||
background: !root.enabled ? #eceef0
|
||||
: button-touch.pressed ? (root.primary ? #82b8f2 : #d8dadd)
|
||||
: button-touch.has-hover ? (root.primary ? #b8d9fb : #f0f1f2)
|
||||
: root.primary ? #a8d0fa : #e2e4e7;
|
||||
border-width: 1px;
|
||||
border-color: root.primary ? #9bc3ed : #c8cbd0;
|
||||
border-radius: 3px;
|
||||
Text {
|
||||
text: root.text;
|
||||
color: root.enabled ? #24272d : #8e9298;
|
||||
font-size: ImGuiFontStyle.body;
|
||||
overflow: elide;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
button-touch := TouchArea { enabled: root.enabled; clicked => { root.clicked(); } }
|
||||
}
|
||||
|
||||
export component PrimaryButton inherits Rectangle {
|
||||
in property <string> text;
|
||||
in property <bool> enabled: true;
|
||||
callback clicked;
|
||||
|
||||
min-width: 72px;
|
||||
height: 30px;
|
||||
background: !root.enabled ? #b8c1d1
|
||||
: touch.pressed ? #1849a9
|
||||
: touch.has-hover ? #2970d6
|
||||
: #2463c7;
|
||||
border-radius: 5px;
|
||||
|
||||
Text {
|
||||
text: root.text;
|
||||
color: white;
|
||||
font-size: ImGuiFontStyle.body;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
enabled: root.enabled;
|
||||
clicked => { root.clicked(); }
|
||||
}
|
||||
}
|
||||
|
||||
export component SecondaryButton inherits Rectangle {
|
||||
in property <string> text;
|
||||
in property <bool> enabled: true;
|
||||
callback clicked;
|
||||
|
||||
min-width: 72px;
|
||||
height: 30px;
|
||||
background: !root.enabled ? #f1f2f4
|
||||
: touch.pressed ? #dfe3e9
|
||||
: touch.has-hover ? #f3f4f6 : white;
|
||||
border-width: 1px;
|
||||
border-color: #c9ced7;
|
||||
border-radius: 5px;
|
||||
clip: true;
|
||||
|
||||
Text {
|
||||
text: root.text;
|
||||
color: root.enabled ? #24272d : #949aa5;
|
||||
font-size: ImGuiFontStyle.body;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
enabled: root.enabled;
|
||||
clicked => { root.clicked(); }
|
||||
}
|
||||
}
|
||||
|
||||
export component ToggleSwitch inherits Rectangle {
|
||||
in-out property <bool> checked;
|
||||
in property <bool> enabled: true;
|
||||
callback toggled(bool);
|
||||
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
background: !root.enabled ? #d0d5dd : root.checked ? #2463c7 : #98a2b3;
|
||||
border-radius: self.height / 2;
|
||||
|
||||
Rectangle {
|
||||
x: root.checked ? parent.width - self.width - 3px : 3px;
|
||||
y: 3px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 7px;
|
||||
background: white;
|
||||
animate x { duration: 100ms; easing: ease-in-out; }
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
enabled: root.enabled;
|
||||
clicked => {
|
||||
root.checked = !root.checked;
|
||||
root.toggled(root.checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component SectionTitle inherits Text {
|
||||
color: #00000080;
|
||||
font-size: ImGuiFontStyle.section-title;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
export component DialogSurface inherits Rectangle {
|
||||
background: white;
|
||||
border-width: 1px;
|
||||
border-color: ImGuiLineStyle.border;
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Single AOT entry point. Generating one header for the entire UI avoids
|
||||
// duplicate imported-component definitions when C++ uses all three windows.
|
||||
export { MainWindow, RecentConnection, UiStrings } from "main_window.slint";
|
||||
export { StreamWindow, StreamTab, FileTransferEntry, NetworkStatsRow, StreamStrings } from "stream_window.slint";
|
||||
export { ServerWindow, ControllerEntry } from "server_window.slint";
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M2 9h13m-5-5 5 5-5 5" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 189 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 22"><path d="M3 11h25M20 3l8 8-8 8" fill="none" stroke="#050505" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 195 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m2 8 3.2 3.2L14 2.8" fill="none" stroke="#2389e8" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 193 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 8"><path d="M1 2l4 4 4-4" fill="none" stroke="#111" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 182 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14"><path d="m8 1-6 6 6 6" fill="none" stroke="#202124" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 186 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14"><path d="m2 1 6 6-6 6" fill="none" stroke="#202124" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 186 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 9"><path d="m1 7 5-5 5 5" fill="none" stroke="#202124" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 185 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3 3l10 10M13 3L3 13" fill="none" stroke="#fff" stroke-width="1.7"/></svg>
|
||||
|
After Width: | Height: | Size: 144 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3 3l10 10M13 3L3 13" fill="none" stroke="#000000" stroke-width="1"/></svg>
|
||||
|
After Width: | Height: | Size: 145 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M7 7H2m5 0V2m4 5h5m-5 0V2M7 11H2m5 0v5m4-5h5m-5 0v5" fill="none" stroke="#202124" stroke-width="1.5"/></svg>
|
||||
|
After Width: | Height: | Size: 178 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><rect x="6" y="3" width="9" height="11" rx="1.2" fill="#111"/><rect x="3" y="6" width="9" height="10" rx="1.2" fill="#111" stroke="#eff0f2" stroke-width="1.4"/></svg>
|
||||
|
After Width: | Height: | Size: 227 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M7 2H2v5M11 2h5v5M7 16H2v-5m9 5h5v-5" fill="none" stroke="#202124" stroke-width="1.5"/><path d="M2 2l5 5m9-5-5 5M2 16l5-5m9 5-5-5" stroke="#202124" stroke-width="1.3"/></svg>
|
||||
|
After Width: | Height: | Size: 244 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M1.5 9s2.7-4.5 7.5-4.5S16.5 9 16.5 9 13.8 13.5 9 13.5 1.5 9 1.5 9Z" fill="none" stroke="#111" stroke-width="1.6"/><path d="M2 2l14 14" stroke="#111" stroke-width="2"/></svg>
|
||||
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M1.5 9s2.7-4.5 7.5-4.5S16.5 9 16.5 9 13.8 13.5 9 13.5 1.5 9 1.5 9Z" fill="none" stroke="#111" stroke-width="1.8"/><circle cx="9" cy="9" r="2.4" fill="#111"/></svg>
|
||||
|
After Width: | Height: | Size: 233 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M4 1h7l3 3v13H4V1Z" fill="none" stroke="#202124" stroke-width="1.4"/><path d="M11 1v4h4M9 14V8m-3 3 3-3 3 3" fill="none" stroke="#202124" stroke-width="1.4"/></svg>
|
||||
|
After Width: | Height: | Size: 234 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 16"><rect x="1" y="3" width="16" height="11" rx="1.5" fill="none" stroke="#202124" stroke-width="1.4"/><path d="M4 6h1m2 0h1m2 0h1m2 0h1M4 9h1m2 0h1m2 0h1m2 0h1M5 12h8" stroke="#202124" stroke-width="1.2" stroke-linecap="round"/></svg>
|
||||
|
After Width: | Height: | Size: 292 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect x="3" y="3" width="10" height="10" fill="none" stroke="#000000" stroke-width="1"/></svg>
|
||||
|
After Width: | Height: | Size: 155 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M2 4h12M2 8h12M2 12h12" fill="none" stroke="#000000" stroke-width="1" stroke-linecap="square"/></svg>
|
||||
|
After Width: | Height: | Size: 171 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M2 8h12" fill="none" stroke="#000000" stroke-width="1"/></svg>
|
||||
|
After Width: | Height: | Size: 132 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><rect x="2" y="3" width="14" height="10" rx="1" fill="none" stroke="#202124" stroke-width="1.5"/><path d="M6 16h6M9 13v3" stroke="#202124" stroke-width="1.5"/></svg>
|
||||
|
After Width: | Height: | Size: 226 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><rect x="4" y="1" width="10" height="16" rx="5" fill="none" stroke="#202124" stroke-width="1.4"/><path d="M2 2l14 14" stroke="#202124" stroke-width="1.7"/></svg>
|
||||
|
After Width: | Height: | Size: 222 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 18"><rect x="3" y="1" width="10" height="16" rx="5" fill="none" stroke="#202124" stroke-width="1.5"/><path d="M8 2v5" stroke="#202124" stroke-width="1.5"/></svg>
|
||||
|
After Width: | Height: | Size: 218 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M3 12.8 11.9 4l2.2 2.2-8.8 8.9-3.1.7.8-3Zm9.7-9.6 1-1a1.4 1.4 0 0 1 2 0l.2.2a1.4 1.4 0 0 1 0 2l-1 1-2.2-2.2Z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M3 12.8 11.9 4l2.2 2.2-8.8 8.9-3.1.7.8-3Z" fill="#111"/><path d="m12.7 3.2 1-1a1.4 1.4 0 0 1 2 0l.2.2a1.4 1.4 0 0 1 0 2l-1 1-2.2-2.2Z" fill="#111"/></svg>
|
||||
|
After Width: | Height: | Size: 224 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M2 16V9h3v7H2Zm5 0V5h3v11H7Zm5 0V2h3v14h-3Z" fill="#202124"/></svg>
|
||||
|
After Width: | Height: | Size: 137 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M5 5h8l-.6 10H5.6L5 5Zm2-2h4l1 1H6l1-1ZM4 4h10v1H4V4Z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 144 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M5 5h8l-.6 10H5.6L5 5Zm2-2h4l1 1H6l1-1ZM4 4h10v1H4V4Z" fill="#111"/></svg>
|
||||
|
After Width: | Height: | Size: 144 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M2 7h3l4-4v12l-4-4H2V7Z" fill="#202124"/><path d="M12 6l4 6m0-6-4 6" stroke="#202124" stroke-width="1.5"/></svg>
|
||||
|
After Width: | Height: | Size: 182 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M2 7h3l4-4v12l-4-4H2V7Z" fill="#202124"/><path d="M12 6a4 4 0 0 1 0 6m2-8a7 7 0 0 1 0 10" fill="none" stroke="#202124" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
|
After Width: | Height: | Size: 238 B |
@@ -0,0 +1,149 @@
|
||||
import { CompactComboBox, FontAwesomeIcons, IconButton, SecondaryButton, ImGuiLineStyle, ImGuiFontStyle } from "common.slint";
|
||||
|
||||
export struct ControllerEntry {
|
||||
remote-id: string,
|
||||
display-name: string,
|
||||
}
|
||||
|
||||
export component ServerWindow inherits Window {
|
||||
title: "CrossDesk";
|
||||
preferred-width: root.language-index == 0 ? 250px : root.language-index == 1 ? 330px : 430px;
|
||||
preferred-height: 150px;
|
||||
min-width: root.language-index == 0 ? 250px : root.language-index == 1 ? 330px : 430px;
|
||||
max-width: root.language-index == 0 ? 250px : root.language-index == 1 ? 330px : 430px;
|
||||
no-frame: true;
|
||||
always-on-top: true;
|
||||
background: transparent;
|
||||
default-font-size: ImGuiFontStyle.base;
|
||||
|
||||
in property <[ControllerEntry]> controllers;
|
||||
in property <[string]> controller-names;
|
||||
in property <int> language-index: 0;
|
||||
in-out property <int> selected-controller: 0;
|
||||
in property <string> connection-label: "Connection status";
|
||||
in property <string> connection-status: "";
|
||||
in property <string> controller-label: "Controller";
|
||||
in property <string> file-transfer-label: "File transfer";
|
||||
in property <string> select-file-label: "Select file";
|
||||
in property <bool> sending-file: false;
|
||||
in property <bool> file-transfer-visible: false;
|
||||
in property <float> file-progress: 0;
|
||||
in property <string> file-progress-text: "";
|
||||
in property <string> current-file-name: "";
|
||||
in property <string> file-size-text: "";
|
||||
|
||||
callback title-drag(int, length, length);
|
||||
callback toggle-collapsed(bool);
|
||||
callback controller-selected(int);
|
||||
callback select-file;
|
||||
callback disconnect-controller;
|
||||
|
||||
private property <bool> collapsed: false;
|
||||
private property <bool> transfer-hovered: false;
|
||||
|
||||
Rectangle {
|
||||
width: root.width;
|
||||
height: root.height;
|
||||
background: white;
|
||||
border-width: 1px;
|
||||
border-color: ImGuiLineStyle.border;
|
||||
border-radius: 8px;
|
||||
clip: true;
|
||||
|
||||
Rectangle {
|
||||
x: 0px;
|
||||
y: 0px;
|
||||
width: parent.width;
|
||||
height: 30px;
|
||||
background: #f8f9fb;
|
||||
Rectangle { y: parent.height - 1px; height: 1px; background: ImGuiLineStyle.border; }
|
||||
IconButton {
|
||||
x: 0px;
|
||||
y: 0px;
|
||||
width: 30px; height: 30px;
|
||||
icon: root.collapsed ? FontAwesomeIcons.angle-down : FontAwesomeIcons.angle-up;
|
||||
icon-size: 12px;
|
||||
clicked => { root.collapsed = !root.collapsed; root.toggle-collapsed(root.collapsed); }
|
||||
}
|
||||
drag := TouchArea {
|
||||
x: 30px; width: parent.width - 30px;
|
||||
property <int> phase: self.pressed ? 1 : 0;
|
||||
changed phase => { root.title-drag(phase, self.mouse-x, self.mouse-y); }
|
||||
moved => { if (self.pressed) { root.title-drag(2, self.mouse-x, self.mouse-y); } }
|
||||
}
|
||||
}
|
||||
|
||||
if !root.collapsed: Rectangle {
|
||||
x: 12px; y: 38px; width: parent.width - 52px; height: 101px;
|
||||
background: white; border-width: 1px; border-color: ImGuiLineStyle.border; border-radius: 5px;
|
||||
VerticalLayout {
|
||||
padding: 8px; spacing: 3px; alignment: start;
|
||||
HorizontalLayout {
|
||||
height: 24px; spacing: 5px;
|
||||
Text { width: root.language-index == 0 ? 69px : 110px; text: root.controller-label; color: #434953; font-size: ImGuiFontStyle.body; vertical-alignment: center; overflow: elide; }
|
||||
CompactComboBox {
|
||||
width: root.language-index == 0 ? 108px : root.language-index == 1 ? 147px : 247px;
|
||||
model: root.controller-names;
|
||||
current-index <=> root.selected-controller;
|
||||
selected(index) => { root.controller-selected(index); }
|
||||
}
|
||||
}
|
||||
Rectangle { height: 1px; background: ImGuiLineStyle.separator; }
|
||||
HorizontalLayout {
|
||||
height: 24px; spacing: 5px;
|
||||
Text { width: root.language-index == 0 ? 69px : 110px; text: root.connection-label; color: #434953; font-size: ImGuiFontStyle.body; vertical-alignment: center; overflow: elide; }
|
||||
Text { text: root.connection-status; color: #2463c7; font-size: ImGuiFontStyle.body; horizontal-alignment: left; vertical-alignment: center; overflow: elide; }
|
||||
}
|
||||
Rectangle { height: 1px; background: ImGuiLineStyle.separator; }
|
||||
Rectangle {
|
||||
height: 24px;
|
||||
HorizontalLayout {
|
||||
y: 0px;
|
||||
width: parent.width;
|
||||
height: 24px;
|
||||
spacing: 5px;
|
||||
Text { width: root.language-index == 0 ? 69px : 110px; text: root.file-transfer-label; color: #434953; font-size: ImGuiFontStyle.body; vertical-alignment: center; overflow: elide; }
|
||||
SecondaryButton { width: root.language-index == 0 ? 78px : root.language-index == 1 ? 140px : 205px; height: 24px; text: root.select-file-label; clicked => { root.select-file(); } }
|
||||
Rectangle {
|
||||
visible: root.file-transfer-visible;
|
||||
width: 35px;
|
||||
Text {
|
||||
text: root.sending-file ? "↑ " + round(root.file-progress * 100) + "%" : "✓";
|
||||
color: root.sending-file ? #2463c7 : #25874b;
|
||||
font-size: ImGuiFontStyle.small;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
transfer-touch := TouchArea {
|
||||
changed has-hover => { root.transfer-hovered = self.has-hover; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !root.collapsed: IconButton {
|
||||
x: parent.width - 34px; y: 38px; width: 24px; height: 101px;
|
||||
icon: FontAwesomeIcons.xmark;
|
||||
icon-size: 13px;
|
||||
icon-color: white;
|
||||
normal-background: #ef2b2d;
|
||||
hover-background: #d92d20;
|
||||
pressed-background: #b42318;
|
||||
border-radius: 5px;
|
||||
clicked => { root.disconnect-controller(); }
|
||||
}
|
||||
if root.file-transfer-visible && root.transfer-hovered: Rectangle {
|
||||
x: 15px; y: 67px; width: root.width - 30px; height: 70px;
|
||||
background: #22252bf2;
|
||||
border-radius: 5px;
|
||||
z: 20;
|
||||
VerticalLayout {
|
||||
padding: 8px; spacing: 3px;
|
||||
Text { text: root.current-file-name; color: white; font-size: ImGuiFontStyle.small; overflow: elide; }
|
||||
Text { text: root.file-size-text; color: #e1e4e8; font-size: ImGuiFontStyle.small; }
|
||||
Text { text: round(root.file-progress * 100) + "% " + root.file-progress-text; color: #e1e4e8; font-size: ImGuiFontStyle.small; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
#ifndef CROSSDESK_GUI_UI_UI_LOCALIZATION_H_
|
||||
#define CROSSDESK_GUI_UI_UI_LOCALIZATION_H_
|
||||
|
||||
#include <slint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "crossdesk_ui.h"
|
||||
#include "localization.h"
|
||||
|
||||
namespace crossdesk::ui_localization {
|
||||
|
||||
inline slint::SharedString Text(const std::string& value) {
|
||||
return slint::SharedString(value);
|
||||
}
|
||||
|
||||
inline int ApplyMainWindowStrings(
|
||||
const slint::ComponentHandle<ui::MainWindow>& window,
|
||||
int language_index) {
|
||||
const int language =
|
||||
localization::detail::ClampLanguageIndex(language_index);
|
||||
auto& strings = window->global<ui::UiStrings>();
|
||||
strings.set_local_desktop(Text(localization::local_desktop[language]));
|
||||
strings.set_remote_desktop(Text(localization::remote_desktop[language]));
|
||||
strings.set_recent_connections(
|
||||
Text(localization::recent_connections[language]));
|
||||
strings.set_local_id(Text(localization::local_id[language]));
|
||||
strings.set_device_name(Text(localization::device_name[language]));
|
||||
strings.set_remote_id(Text(localization::remote_id[language]));
|
||||
strings.set_online(Text(localization::online[language]));
|
||||
strings.set_offline(Text(localization::offline[language]));
|
||||
strings.set_password(Text(localization::password[language]));
|
||||
strings.set_copied(
|
||||
Text(localization::local_id_copied_to_clipboard[language]));
|
||||
strings.set_connect(Text(localization::connect[language]));
|
||||
strings.set_settings(Text(localization::settings[language]));
|
||||
strings.set_about(Text(localization::about[language]));
|
||||
strings.set_ok(Text(localization::ok[language]));
|
||||
strings.set_cancel(Text(localization::cancel[language]));
|
||||
strings.set_new_password(Text(localization::new_password[language]));
|
||||
strings.set_invalid_password(Text(localization::max_password_len[language]));
|
||||
strings.set_edit_alias(
|
||||
Text(localization::input_connection_alias[language]));
|
||||
strings.set_delete_connection(
|
||||
Text(localization::delete_connection[language]));
|
||||
strings.set_confirm_delete(
|
||||
Text(localization::confirm_delete_connection[language]));
|
||||
strings.set_language(Text(localization::language[language]));
|
||||
strings.set_video_quality(Text(localization::video_quality[language]));
|
||||
strings.set_frame_rate(Text(localization::video_frame_rate[language]));
|
||||
strings.set_codec(Text(localization::video_encode_format[language]));
|
||||
strings.set_hardware_codec(
|
||||
Text(localization::enable_hardware_video_codec[language]));
|
||||
strings.set_turn_relay(Text(localization::enable_turn[language]));
|
||||
strings.set_srtp(Text(localization::enable_srtp[language]));
|
||||
strings.set_self_hosted(
|
||||
Text(localization::self_hosted_server_config[language]));
|
||||
strings.set_autostart(Text(localization::enable_autostart[language]));
|
||||
strings.set_daemon(Text(localization::enable_daemon[language]));
|
||||
strings.set_file_save_path(
|
||||
Text(localization::file_transfer_save_path[language]));
|
||||
strings.set_default_desktop(Text(localization::default_desktop[language]));
|
||||
strings.set_server_host(
|
||||
Text(localization::self_hosted_server_address[language]));
|
||||
strings.set_server_port(
|
||||
Text(localization::self_hosted_server_port[language]));
|
||||
strings.set_coturn_port(
|
||||
Text(localization::self_hosted_server_coturn_server_port[language]));
|
||||
strings.set_version(Text(localization::version[language]));
|
||||
strings.set_signal_connected(Text(localization::signal_connected[language]));
|
||||
strings.set_signal_disconnected(
|
||||
Text(localization::signal_disconnected[language]));
|
||||
strings.set_tls_error(Text(localization::signal_tls_cert_error[language]));
|
||||
strings.set_update_available(
|
||||
Text(localization::new_version_available[language]));
|
||||
strings.set_release_notes(Text(localization::release_notes[language]));
|
||||
strings.set_download(Text(localization::update[language]));
|
||||
strings.set_input_password(Text(localization::input_password[language]));
|
||||
strings.set_reinput_password(Text(localization::reinput_password[language]));
|
||||
strings.set_remember_password(
|
||||
Text(localization::remember_password[language]));
|
||||
strings.set_validate_password(
|
||||
Text(localization::validate_password[language]));
|
||||
strings.set_request_permissions(
|
||||
Text(localization::request_permissions[language]));
|
||||
strings.set_permission_required(
|
||||
Text(localization::permission_required_message[language]));
|
||||
strings.set_screen_recording_permission(
|
||||
Text(localization::screen_recording_permission[language]));
|
||||
strings.set_accessibility_permission(
|
||||
Text(localization::accessibility_permission[language]));
|
||||
strings.set_service_setup_title(
|
||||
Text(localization::windows_service_setup_title[language]));
|
||||
strings.set_service_setup_message(
|
||||
Text(localization::windows_service_setup_message[language]));
|
||||
strings.set_service_settings_label(
|
||||
Text(localization::windows_service_settings_label[language]));
|
||||
strings.set_install_service(
|
||||
Text(localization::install_windows_service[language]));
|
||||
strings.set_service_installed(
|
||||
Text(localization::windows_service_installed[language]));
|
||||
strings.set_do_not_remind(
|
||||
Text(localization::do_not_remind_again[language]));
|
||||
strings.set_notification(Text(localization::notification[language]));
|
||||
strings.set_service_suppressed_message(
|
||||
Text(localization::windows_service_prompt_suppressed_message[language]));
|
||||
strings.set_quality_low(Text(localization::video_quality_low[language]));
|
||||
strings.set_quality_medium(
|
||||
Text(localization::video_quality_medium[language]));
|
||||
strings.set_quality_high(Text(localization::video_quality_high[language]));
|
||||
strings.set_codec_h264(Text(localization::h264[language]));
|
||||
strings.set_codec_av1(Text(localization::av1[language]));
|
||||
strings.set_self_hosted_settings(
|
||||
Text(localization::self_hosted_server_settings[language]));
|
||||
strings.set_access_website(Text(localization::access_website[language]));
|
||||
strings.set_release_date_label(Text(localization::release_date[language]));
|
||||
strings.set_later(Text(localization::cancel[language]));
|
||||
return language;
|
||||
}
|
||||
|
||||
template <typename Window>
|
||||
inline int ApplyStreamWindowStrings(
|
||||
const slint::ComponentHandle<Window>& window, int language_index) {
|
||||
const int language =
|
||||
localization::detail::ClampLanguageIndex(language_index);
|
||||
auto& strings = window->template global<ui::StreamStrings>();
|
||||
strings.set_select_display(Text(localization::select_display[language]));
|
||||
strings.set_send_shortcut(Text(localization::send_shortcut[language]));
|
||||
strings.set_control_mouse(Text(localization::control_mouse[language]));
|
||||
strings.set_release_mouse(Text(localization::release_mouse[language]));
|
||||
strings.set_audio(Text(localization::audio_capture[language]));
|
||||
strings.set_mute(Text(localization::mute[language]));
|
||||
strings.set_select_file(Text(localization::select_file[language]));
|
||||
strings.set_show_stats(
|
||||
Text(localization::show_net_traffic_stats[language]));
|
||||
strings.set_hide_stats(
|
||||
Text(localization::hide_net_traffic_stats[language]));
|
||||
strings.set_fullscreen(Text(localization::fullscreen[language]));
|
||||
strings.set_exit_fullscreen(Text(localization::exit_fullscreen[language]));
|
||||
strings.set_disconnect(Text(localization::disconnect[language]));
|
||||
strings.set_file_transfer(
|
||||
Text(localization::file_transfer_progress[language]));
|
||||
strings.set_expand_control(Text(localization::expand_control_bar[language]));
|
||||
strings.set_collapse_control(
|
||||
Text(localization::collapse_control_bar[language]));
|
||||
strings.set_stats_in(Text(localization::in[language]));
|
||||
strings.set_stats_out(Text(localization::out[language]));
|
||||
strings.set_stats_loss_rate(Text(localization::loss_rate[language]));
|
||||
strings.set_stats_resolution(Text(localization::resolution[language]));
|
||||
strings.set_stats_connection_mode(
|
||||
Text(localization::connection_mode[language]));
|
||||
return language;
|
||||
}
|
||||
|
||||
} // namespace crossdesk::ui_localization
|
||||
|
||||
#endif // CROSSDESK_GUI_UI_UI_LOCALIZATION_H_
|
||||