mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 00:33:06 +08:00
[feat] add mobile codec status and scrolling codec names
This commit is contained in:
@@ -1,5 +1,37 @@
|
||||
import { FlatSurface, UiSurface } from "ui_surface.slint";
|
||||
import { UiRadius } from "ui_radius.slint";
|
||||
|
||||
// Keep codec names readable in narrow status panels without changing row height.
|
||||
export component ScrollingText inherits Rectangle {
|
||||
in property <string> text;
|
||||
in property <color> text-color;
|
||||
in property <length> font-size: 11px;
|
||||
in property <int> font-weight: 400;
|
||||
in property <bool> align-right: false;
|
||||
private property <length> distance: max(0px, label.preferred-width - root.width);
|
||||
private property <float> phase: 0;
|
||||
min-width: 0px;
|
||||
clip: true;
|
||||
accessible-role: AccessibleRole.text;
|
||||
accessible-label: root.text;
|
||||
changed text => { root.phase = 0; }
|
||||
changed width => { root.phase = 0; }
|
||||
Timer {
|
||||
interval: 16ms;
|
||||
running: root.visible && root.width > 0px && root.distance > 0px;
|
||||
// 30 logical pixels/second, with a one-second pause at each end.
|
||||
triggered => { root.phase = mod(root.phase + 0.48, root.distance / 1px + 60); }
|
||||
}
|
||||
label := Text {
|
||||
x: root.distance > 0px ? -min(root.distance, max(0, root.phase - 30) * 1px)
|
||||
: root.align-right ? root.width - self.preferred-width : 0px;
|
||||
width: self.preferred-width; height: root.height;
|
||||
text: root.text; color: root.text-color;
|
||||
font-size: root.font-size; font-weight: root.font-weight;
|
||||
wrap: no-wrap; vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
export global FloatingBarStyle {
|
||||
out property <length> corner-radius: UiRadius.menu;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SurfaceSeparator, FlatSurface, UiSurface } from "ui_surface.slint";
|
||||
import { UiRadius } from "ui_radius.slint";
|
||||
import { GalleryStrings } from "device_strings.slint";
|
||||
import { CompactButton } from "common.slint";
|
||||
import { CompactButton, ScrollingText } from "common.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
export struct GalleryNetworkDevice {
|
||||
@@ -153,18 +153,26 @@ component NetworkRow inherits Rectangle {
|
||||
spacing: 12px;
|
||||
Rectangle {
|
||||
width: (parent.width - 12px) / 2;
|
||||
Text {
|
||||
width: parent.width; height: parent.height;
|
||||
text: GalleryStrings.tr("被控端编码器 ", "Remote encoder ", "Удалённый кодер ") + root.device.encoder;
|
||||
font-size: 10px; color: #586f87; vertical-alignment: center; overflow: elide;
|
||||
encoder-label := Text {
|
||||
height: parent.height; width: self.preferred-width;
|
||||
text: GalleryStrings.tr("被控端编码器 ", "Remote encoder ", "Удалённый кодер ");
|
||||
font-size: 10px; color: #586f87; vertical-alignment: center;
|
||||
}
|
||||
ScrollingText {
|
||||
x: encoder-label.width; width: max(0px, parent.width - self.x); height: parent.height;
|
||||
text: root.device.encoder; font-size: 10px; text-color: #586f87;
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
width: (parent.width - 12px) / 2;
|
||||
Text {
|
||||
width: parent.width; height: parent.height;
|
||||
text: GalleryStrings.tr("控制端解码器 ", "Local decoder ", "Локальный декодер ") + root.device.decoder;
|
||||
font-size: 10px; color: #586f87; vertical-alignment: center; overflow: elide;
|
||||
decoder-label := Text {
|
||||
height: parent.height; width: self.preferred-width;
|
||||
text: GalleryStrings.tr("控制端解码器 ", "Local decoder ", "Локальный декодер ");
|
||||
font-size: 10px; color: #586f87; vertical-alignment: center;
|
||||
}
|
||||
ScrollingText {
|
||||
x: decoder-label.width; width: max(0px, parent.width - self.x); height: parent.height;
|
||||
text: root.device.decoder; font-size: 10px; text-color: #586f87;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SurfaceSeparator, FlatSurface, UiSurface } from "ui_surface.slint";
|
||||
import { UiRadius } from "ui_radius.slint";
|
||||
import { ComboBox, ScrollView } from "std-widgets.slint";
|
||||
import { RemoteCursor } from "remote_cursor.slint";
|
||||
import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiFontStyle, ControlStyle, ButtonStyle } from "common.slint";
|
||||
import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiFontStyle, ControlStyle, ButtonStyle, ScrollingText } from "common.slint";
|
||||
|
||||
export struct StreamTab {
|
||||
remote-id: string,
|
||||
@@ -1458,13 +1458,12 @@ export component StreamWindow inherits Window {
|
||||
color: #667085; font-size: ImGuiFontStyle.small;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
Text {
|
||||
ScrollingText {
|
||||
x: codec-label-width-probe.preferred-width + 8px; y: 0px;
|
||||
width: parent.width - self.x; height: parent.height;
|
||||
text: codec.value; color: #344054;
|
||||
text: codec.value; text-color: #344054;
|
||||
font-size: ImGuiFontStyle.body; font-weight: 500;
|
||||
horizontal-alignment: right;
|
||||
vertical-alignment: center; overflow: elide;
|
||||
align-right: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ int main() {
|
||||
auto& adapter = *backend->adapter;
|
||||
stream->window().dispatch_resize_event(slint::LogicalSize({1280, 720}));
|
||||
stream->show();
|
||||
stream->set_secure_attention_supported(false);
|
||||
stream->set_privacy_visible(true);
|
||||
auto rows = std::make_shared<slint::VectorModel<crossdesk::ui::NetworkStatsRow>>();
|
||||
stream->set_stats_rows(rows);
|
||||
std::vector<crossdesk::ui::NetworkStatsRow> stats(4);
|
||||
@@ -75,6 +77,13 @@ int main() {
|
||||
};
|
||||
settle();
|
||||
const float top = stream->get_video_area_top();
|
||||
// The initial control is only the logo. Wait for the toolbar reveal before
|
||||
// clicking network status, just as a user does in a new session.
|
||||
const slint::LogicalPosition logo({19, top + 19});
|
||||
stream->window().dispatch_pointer_move_event(logo);
|
||||
stream->window().dispatch_pointer_press_event(logo, slint::PointerEventButton::Left);
|
||||
stream->window().dispatch_pointer_release_event(logo, slint::PointerEventButton::Left);
|
||||
settle(20);
|
||||
for (bool dock_right : {false, true}) {
|
||||
if (dock_right) {
|
||||
stream->invoke_begin_control_drag(5, 20);
|
||||
@@ -82,19 +91,26 @@ int main() {
|
||||
stream->invoke_end_control_drag();
|
||||
settle();
|
||||
}
|
||||
const int control_x = dock_right ? 916 : 0;
|
||||
const slint::LogicalPosition button({float(control_x + (dock_right ? 278 : 245)), top + 19});
|
||||
// With privacy visible and no Windows shortcuts, the toolbar is 344 px.
|
||||
// Its logo moves to the trailing edge when docked on the right.
|
||||
const int control_x = dock_right ? int(stream->get_video_area_width()) - 344 : 0;
|
||||
const slint::LogicalPosition button({float(control_x + (dock_right ? 214 : 250)), top + 19});
|
||||
stream->window().dispatch_pointer_move_event(button);
|
||||
settle(35); // Include the tooltip that appears before the click.
|
||||
for (bool expanded : {true, false, true, false}) {
|
||||
stream->window().dispatch_pointer_press_event(button, slint::PointerEventButton::Left);
|
||||
stream->window().dispatch_pointer_release_event(button, slint::PointerEventButton::Left);
|
||||
settle(); // No pointer movement: animation must finish on its own.
|
||||
if (stats_visible != expanded) {
|
||||
std::cerr << "Network status button did not toggle the panel; dock_right="
|
||||
<< dock_right << '\n';
|
||||
return 1;
|
||||
}
|
||||
for (int y : {65, 195}) {
|
||||
const auto pixel = adapter.pixels[(int(top) + y) * 1280 + control_x + 4];
|
||||
const bool painted = pixel.r >= 240 && pixel.g >= 240 && pixel.b >= 240;
|
||||
const bool cleared = pixel.r == 0 && pixel.g == 0 && pixel.b == 0;
|
||||
if (stats_visible != expanded || (expanded ? !painted : !cleared)) {
|
||||
if (expanded ? !painted : !cleared) {
|
||||
std::cerr << "Stats must fully " << (expanded ? "expand" : "collapse")
|
||||
<< " without pointer movement; dock_right=" << dock_right
|
||||
<< ", y=" << y << ", pixel=" << int(pixel.r) << ','
|
||||
|
||||
@@ -779,7 +779,8 @@ bool VerifyWorkbenchActions() {
|
||||
if (window->get_settings_tab() != 0) return false;
|
||||
window->set_gallery_settings_error("");
|
||||
window->set_settings_open(false);
|
||||
click(60, 20);
|
||||
// DeskHub follows the ID/IP mode switch when direct IP connections are enabled.
|
||||
click(window->get_ip_direct_enabled() ? 204 : 60, 20);
|
||||
if (!gallery_opened) return false;
|
||||
crossdesk::ui::RecentConnection device{};
|
||||
device.remote_id = "987654321";
|
||||
@@ -850,24 +851,45 @@ bool VerifyWorkbenchActions() {
|
||||
bool VerifySystemShortcutMenu() {
|
||||
auto stream = crossdesk::ui::StreamWindow::create();
|
||||
stream->window().set_size(slint::LogicalSize(slint::Size<float>{1280, 720}));
|
||||
stream->show();
|
||||
std::vector<std::string> commands;
|
||||
stream->on_send_shortcut([&](slint::SharedString command) { commands.emplace_back(command); });
|
||||
const auto click = [&](float y) {
|
||||
const slint::LogicalPosition point({55, stream->get_video_area_top() + y});
|
||||
const auto settle = [] {
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
slint::platform::update_timers_and_animations();
|
||||
}
|
||||
};
|
||||
const auto click = [&](float x, float y) {
|
||||
const slint::LogicalPosition point({x, stream->get_video_area_top() + y});
|
||||
stream->window().dispatch_pointer_move_event(point);
|
||||
stream->window().dispatch_pointer_press_event(point, slint::PointerEventButton::Left);
|
||||
stream->window().dispatch_pointer_release_event(point, slint::PointerEventButton::Left);
|
||||
};
|
||||
click(20);
|
||||
if (!stream->invoke_is_local_control_area(55, 90)) return false;
|
||||
click(58);
|
||||
stream->set_secure_attention_supported(false);
|
||||
click(20);
|
||||
if (stream->invoke_is_local_control_area(55, 90)) return false;
|
||||
click(58);
|
||||
// The toolbar starts collapsed. Reveal it before targeting its buttons.
|
||||
click(19, 19);
|
||||
settle();
|
||||
stream->set_secure_attention_supported(true);
|
||||
click(20); click(88);
|
||||
return commands == std::vector<std::string>{"Ctrl+Alt+Del", "Win+L", "Win+L"};
|
||||
settle();
|
||||
click(90, 19);
|
||||
if (!stream->invoke_is_local_control_area(90, 90)) return false;
|
||||
click(90, 68);
|
||||
|
||||
// Switching away from a Windows host hides the entire shortcut entry and
|
||||
// closes an open menu; its former rows must no longer dispatch commands.
|
||||
click(90, 19);
|
||||
if (!stream->invoke_is_local_control_area(90, 90)) return false;
|
||||
stream->set_secure_attention_supported(false);
|
||||
settle();
|
||||
if (stream->invoke_is_local_control_area(90, 90)) return false;
|
||||
click(90, 98);
|
||||
if (commands != std::vector<std::string>{"Ctrl+Alt+Del"}) return false;
|
||||
|
||||
stream->set_secure_attention_supported(true);
|
||||
settle();
|
||||
click(90, 19);
|
||||
click(90, 98);
|
||||
return commands == std::vector<std::string>{"Ctrl+Alt+Del", "Win+L"};
|
||||
}
|
||||
|
||||
bool VerifyRemoteIdEditing() {
|
||||
@@ -905,7 +927,7 @@ bool VerifyRemoteIdEditing() {
|
||||
|
||||
int main() {
|
||||
if (!VerifySystemShortcutMenu()) {
|
||||
std::cerr << "Shortcut menu must omit unsupported SAS and resize its input area\n";
|
||||
std::cerr << "Shortcut menu must dispatch Windows shortcuts and hide for unsupported hosts\n";
|
||||
return 13;
|
||||
}
|
||||
if (!VerifyRemoteIdEditing()) {
|
||||
|
||||
Reference in New Issue
Block a user