[feat] add localized tooltips for control bar buttons

This commit is contained in:
dijunkun
2026-05-27 00:37:34 +08:00
parent 06c53fdc9c
commit 7682ad63e4
3 changed files with 302 additions and 171 deletions
@@ -33,10 +33,15 @@ struct TranslationRow {
X(recent_connections, u8"近期连接", "Recent Connections", \ X(recent_connections, u8"近期连接", "Recent Connections", \
u8"Недавние подключения") \ u8"Недавние подключения") \
X(disconnect, u8"断开连接", "Disconnect", u8"Отключить") \ X(disconnect, u8"断开连接", "Disconnect", u8"Отключить") \
X(select_display, u8"选择显示器", "Select Display", u8"Выбрать дисплей") \
X(expand_control_bar, u8"展开控制栏", "Expand Control Bar", \
u8"Развернуть панель управления") \
X(collapse_control_bar, u8"收起控制栏", "Collapse Control Bar", \
u8"Свернуть панель управления") \
X(fullscreen, u8"全屏", " Fullscreen", u8"Полный экран") \ X(fullscreen, u8"全屏", " Fullscreen", u8"Полный экран") \
X(show_net_traffic_stats, u8"显示流量统计", "Show Net Traffic Stats", \ X(show_net_traffic_stats, u8"显示网络状态", "Show Net Traffic Stats", \
u8"Показать статистику трафика") \ u8"Показать статистику трафика") \
X(hide_net_traffic_stats, u8"隐藏流量统计", "Hide Net Traffic Stats", \ X(hide_net_traffic_stats, u8"隐藏网络状态", "Hide Net Traffic Stats", \
u8"Скрыть статистику трафика") \ u8"Скрыть статистику трафика") \
X(video, u8"视频", "Video", u8"Видео") \ X(video, u8"视频", "Video", u8"Видео") \
X(audio, u8"音频", "Audio", u8"Аудио") \ X(audio, u8"音频", "Audio", u8"Аудио") \
@@ -47,9 +52,9 @@ struct TranslationRow {
X(loss_rate, u8"丢包率", "Loss Rate", u8"Потери пакетов") \ X(loss_rate, u8"丢包率", "Loss Rate", u8"Потери пакетов") \
X(exit_fullscreen, u8"退出全屏", "Exit fullscreen", \ X(exit_fullscreen, u8"退出全屏", "Exit fullscreen", \
u8"Выйти из полноэкранного режима") \ u8"Выйти из полноэкранного режима") \
X(control_mouse, u8"控制", "Control", u8"Управление") \ X(control_mouse, u8"控制鼠标", "Control Mouse", u8"Управление мышью") \
X(release_mouse, u8"释放", "Release", u8"Освободить") \ X(release_mouse, u8"释放鼠标", "Release Mouse", u8"Освободить мышь") \
X(audio_capture, u8"声音", "Audio", u8"Звук") \ X(audio_capture, u8"播放声音", "Audio Capture", u8"Воспроизведение звука") \
X(mute, u8" 静音", " Mute", u8"Без звука") \ X(mute, u8" 静音", " Mute", u8"Без звука") \
X(send_shortcut, u8"发送组合键", "Send Shortcut", u8"Сочетания клавиш") \ X(send_shortcut, u8"发送组合键", "Send Shortcut", u8"Сочетания клавиш") \
X(send_sas, u8"发送SAS", "Send SAS", u8"Отправить SAS") \ X(send_sas, u8"发送SAS", "Send SAS", u8"Отправить SAS") \
@@ -60,15 +65,15 @@ struct TranslationRow {
"Remote lock screen visible, send SAS", \ "Remote lock screen visible, send SAS", \
u8"Видна блокировка, отправьте SAS") \ u8"Видна блокировка, отправьте SAS") \
X(remote_secure_desktop_active, u8"远端已进入安全桌面", \ X(remote_secure_desktop_active, u8"远端已进入安全桌面", \
"Remote secure desktop active", \ "Remote secure desktop active", u8"Активен защищенный рабочий стол") \
u8"Активен защищенный рабочий стол") \
X(remote_service_unavailable, u8"远端Windows服务不可用", \ X(remote_service_unavailable, u8"远端Windows服务不可用", \
"Remote Windows service unavailable", \ "Remote Windows service unavailable", \
u8"Служба Windows на удаленной стороне недоступна") \ u8"Служба Windows на удаленной стороне недоступна") \
X(remote_unlock_requires_secure_desktop, \ X(remote_unlock_requires_secure_desktop, \
u8"当前仍需要安全桌面专用采集/输入", \ u8"当前仍需要安全桌面专用采集/输入", \
"Secure desktop capture/input is still required", \ "Secure desktop capture/input is still required", \
u8"По-прежнему нужен отдельный захват/ввод для защищенного рабочего стола") \ u8"По-прежнему нужен отдельный захват/ввод для защищенного рабочего " \
u8"стола") \
X(settings, u8"设置", "Settings", u8"Настройки") \ X(settings, u8"设置", "Settings", u8"Настройки") \
X(language, u8"语言:", "Language:", u8"Язык:") \ X(language, u8"语言:", "Language:", u8"Язык:") \
X(video_quality, u8"视频质量:", "Video Quality:", u8"Качество видео:") \ X(video_quality, u8"视频质量:", "Video Quality:", u8"Качество видео:") \
@@ -136,7 +141,8 @@ struct TranslationRow {
X(enable_daemon, u8"启用守护进程:", "Enable Daemon:", u8"Включить демон:") \ X(enable_daemon, u8"启用守护进程:", "Enable Daemon:", u8"Включить демон:") \
X(takes_effect_after_restart, u8"重启后生效", "Takes effect after restart", \ X(takes_effect_after_restart, u8"重启后生效", "Takes effect after restart", \
u8"Вступит в силу после перезапуска") \ u8"Вступит в силу после перезапуска") \
X(select_file, u8"选择文件", "Select File", u8"Выбрать файл") \ X(select_file, u8"选择文件发送", "Select File to Send", \
u8"Выбрать файл для отправки") \
X(file_transfer_progress, u8"文件传输进度", "File Transfer Progress", \ X(file_transfer_progress, u8"文件传输进度", "File Transfer Progress", \
u8"Прогресс передачи файлов") \ u8"Прогресс передачи файлов") \
X(queued, u8"队列中", "Queued", u8"В очереди") \ X(queued, u8"队列中", "Queued", u8"В очереди") \
+59 -20
View File
@@ -15,6 +15,22 @@
namespace crossdesk { namespace crossdesk {
namespace {
void ShowControlBarTooltip(const std::string& text) {
if (!ImGui::IsItemHovered() || text.empty()) {
return;
}
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::Text("%s", text.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
} // namespace
int CountDigits(int number) { int CountDigits(int number) {
if (number == 0) return 1; if (number == 0) return 1;
return (int)std::floor(std::log10(std::abs(number))) + 1; return (int)std::floor(std::log10(std::abs(number))) + 1;
@@ -162,6 +178,8 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImVec2 btn_min = ImGui::GetItemRectMin(); ImVec2 btn_min = ImGui::GetItemRectMin();
ImVec2 btn_size_actual = ImGui::GetItemRectSize(); ImVec2 btn_size_actual = ImGui::GetItemRectSize();
ShowControlBarTooltip(
localization::select_display[localization_language_index_]);
props->display_selectable_hovered_ = false; props->display_selectable_hovered_ = false;
if (ImGui::BeginPopup("display")) { if (ImGui::BeginPopup("display")) {
@@ -185,12 +203,12 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImGui::EndPopup(); ImGui::EndPopup();
} }
ImGui::SetWindowFontScale(0.5f); ImGui::SetWindowFontScale(0.4f);
ImVec2 text_size = ImGui::CalcTextSize( ImVec2 text_size = ImGui::CalcTextSize(
std::to_string(props->selected_display_ + 1).c_str()); std::to_string(props->selected_display_ + 1).c_str());
ImVec2 text_pos = ImVec2 text_pos =
ImVec2(btn_min.x + (btn_size_actual.x - text_size.x) * 0.5f, ImVec2(btn_min.x + (btn_size_actual.x - text_size.x) * 0.53f,
btn_min.y + (btn_size_actual.y - text_size.y) * 0.35f); btn_min.y + (btn_size_actual.y - text_size.y) * 0.33f);
ImGui::GetWindowDrawList()->AddText( ImGui::GetWindowDrawList()->AddText(
text_pos, IM_COL32(0, 0, 0, 255), text_pos, IM_COL32(0, 0, 0, 255),
std::to_string(props->selected_display_ + 1).c_str()); std::to_string(props->selected_display_ + 1).c_str());
@@ -218,25 +236,14 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
if (ImGui::Button(shortcut.c_str(), ImVec2(button_width, button_height))) { if (ImGui::Button(shortcut.c_str(), ImVec2(button_width, button_height))) {
ImGui::OpenPopup("shortcut"); ImGui::OpenPopup("shortcut");
} }
ShowControlBarTooltip(
if (ImGui::IsItemHovered()) { localization::send_shortcut[localization_language_index_]);
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::Text(
"%s",
localization::send_shortcut[localization_language_index_].c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
props->shortcut_selectable_hovered_ = false; props->shortcut_selectable_hovered_ = false;
if (ImGui::BeginPopup("shortcut")) { if (ImGui::BeginPopup("shortcut")) {
ImGui::SetWindowFontScale(0.5f); ImGui::SetWindowFontScale(0.5f);
std::string sas_label = std::string sas_label = "Ctrl+Alt+Del";
"Ctrl+Alt+Del - " + std::string lock_label = "Win+L";
localization::send_sas[localization_language_index_];
std::string lock_label =
"Win+L - " + localization::lock_remote[localization_language_index_];
if (ImGui::Selectable(sas_label.c_str())) { if (ImGui::Selectable(sas_label.c_str())) {
send_service_command(ServiceCommandFlag::send_sas, "SAS"); send_service_command(ServiceCommandFlag::send_sas, "SAS");
} }
@@ -268,6 +275,12 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
: localization::control_mouse[localization_language_index_]; : localization::control_mouse[localization_language_index_];
} }
} }
const bool mouse_button_hovered = ImGui::IsItemHovered();
const std::string mouse_tooltip =
props->enable_mouse_control_
? localization::release_mouse[localization_language_index_]
: localization::control_mouse[localization_language_index_];
ShowControlBarTooltip(mouse_tooltip);
if (!props->enable_mouse_control_) { if (!props->enable_mouse_control_) {
draw_list->AddLine(ImVec2(disable_mouse_x, disable_mouse_y), draw_list->AddLine(ImVec2(disable_mouse_x, disable_mouse_y),
@@ -280,7 +293,7 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImVec2( ImVec2(
mouse_x + button_width - line_padding - line_thickness * 0.7f, mouse_x + button_width - line_padding - line_thickness * 0.7f,
mouse_y + button_height - line_padding + line_thickness * 0.7f), mouse_y + button_height - line_padding + line_thickness * 0.7f),
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255) mouse_button_hovered ? IM_COL32(66, 150, 250, 255)
: IM_COL32(179, 213, 253, 255), : IM_COL32(179, 213, 253, 255),
line_thickness); line_thickness);
} }
@@ -313,6 +326,12 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
props->control_data_label_.c_str()); props->control_data_label_.c_str());
} }
} }
const bool audio_button_hovered = ImGui::IsItemHovered();
const std::string audio_tooltip =
props->audio_capture_button_pressed_
? localization::mute[localization_language_index_]
: localization::audio_capture[localization_language_index_];
ShowControlBarTooltip(audio_tooltip);
if (!props->audio_capture_button_pressed_) { if (!props->audio_capture_button_pressed_) {
draw_list->AddLine(ImVec2(disable_audio_x, disable_audio_y), draw_list->AddLine(ImVec2(disable_audio_x, disable_audio_y),
@@ -325,7 +344,7 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImVec2( ImVec2(
audio_x + button_width - line_padding - line_thickness * 0.7f, audio_x + button_width - line_padding - line_thickness * 0.7f,
audio_y + button_height - line_padding + line_thickness * 0.7f), audio_y + button_height - line_padding + line_thickness * 0.7f),
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255) audio_button_hovered ? IM_COL32(66, 150, 250, 255)
: IM_COL32(179, 213, 253, 255), : IM_COL32(179, 213, 253, 255),
line_thickness); line_thickness);
} }
@@ -339,6 +358,8 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
std::string path = OpenFileDialog(title); std::string path = OpenFileDialog(title);
ProcessSelectedFile(path, props, file_label_); ProcessSelectedFile(path, props, file_label_);
} }
ShowControlBarTooltip(
localization::select_file[localization_language_index_]);
ImGui::SameLine(); ImGui::SameLine();
// net traffic stats button // net traffic stats button
@@ -363,6 +384,12 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
: localization::show_net_traffic_stats : localization::show_net_traffic_stats
[localization_language_index_]; [localization_language_index_];
} }
const std::string net_traffic_stats_tooltip =
props->net_traffic_stats_button_pressed_
? localization::hide_net_traffic_stats[localization_language_index_]
: localization::show_net_traffic_stats
[localization_language_index_];
ShowControlBarTooltip(net_traffic_stats_tooltip);
if (button_color_style_pushed) { if (button_color_style_pushed) {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
@@ -389,6 +416,11 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
} }
props->reset_control_bar_pos_ = true; props->reset_control_bar_pos_ = true;
} }
const std::string fullscreen_tooltip =
fullscreen_button_pressed_
? localization::exit_fullscreen[localization_language_index_]
: localization::fullscreen[localization_language_index_];
ShowControlBarTooltip(fullscreen_tooltip);
ImGui::SameLine(); ImGui::SameLine();
// close button // close button
@@ -398,6 +430,8 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImVec2(button_width, button_height))) { ImVec2(button_width, button_height))) {
CleanupPeer(props); CleanupPeer(props);
} }
ShowControlBarTooltip(
localization::disconnect[localization_language_index_]);
ImGui::SameLine(); ImGui::SameLine();
@@ -427,6 +461,10 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
: ICON_FA_ANGLE_RIGHT) : ICON_FA_ANGLE_RIGHT)
: (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT : (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT
: ICON_FA_ANGLE_LEFT); : ICON_FA_ANGLE_LEFT);
const std::string control_bar_tooltip =
props->control_bar_expand_
? localization::collapse_control_bar[localization_language_index_]
: localization::expand_control_bar[localization_language_index_];
if (ImGui::Button(control_bar.c_str(), if (ImGui::Button(control_bar.c_str(),
ImVec2(button_height * 0.6f, button_height))) { ImVec2(button_height * 0.6f, button_height))) {
props->control_bar_expand_ = !props->control_bar_expand_; props->control_bar_expand_ = !props->control_bar_expand_;
@@ -438,6 +476,7 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
props->net_traffic_stats_button_pressed_ = false; props->net_traffic_stats_button_pressed_ = false;
} }
} }
ShowControlBarTooltip(control_bar_tooltip);
if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) { if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) {
NetTrafficStats(props); NetTrafficStats(props);
+86
View File
@@ -39,6 +39,35 @@ bool ExpectContains(const char* name, const std::string& value,
return false; return false;
} }
bool ExpectNotContains(const char* name, const std::string& value,
const std::string& unexpected) {
if (value.find(unexpected) == std::string::npos) {
return true;
}
std::cerr << name << " contains unexpected text: " << unexpected << "\n";
return false;
}
bool ExpectContainsAtLeast(const char* name, const std::string& value,
const std::string& expected, size_t min_count) {
size_t count = 0;
size_t pos = 0;
while ((pos = value.find(expected, pos)) != std::string::npos) {
++count;
pos += expected.size();
}
if (count >= min_count) {
return true;
}
std::cerr << name << " expected at least " << min_count
<< " occurrences of: " << expected << ", found " << count
<< "\n";
return false;
}
bool ExpectResetBeforeDisplayPopup(const std::string& value) { bool ExpectResetBeforeDisplayPopup(const std::string& value) {
const std::string reset = "props->display_selectable_hovered_ = false;"; const std::string reset = "props->display_selectable_hovered_ = false;";
const std::string popup = "ImGui::BeginPopup(\"display\")"; const std::string popup = "ImGui::BeginPopup(\"display\")";
@@ -93,5 +122,62 @@ int main() {
ok &= ExpectContains("control_bar.cpp", control_bar, ok &= ExpectContains("control_bar.cpp", control_bar,
"props->shortcut_selectable_hovered_ ="); "props->shortcut_selectable_hovered_ =");
ok &= ExpectResetBeforeShortcutPopup(control_bar); ok &= ExpectResetBeforeShortcutPopup(control_bar);
ok &= ExpectContains("control_bar.cpp", control_bar,
"void ShowControlBarTooltip(const std::string& text)");
ok &= ExpectContainsAtLeast("control_bar.cpp", control_bar,
"ShowControlBarTooltip(", 10);
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::select_display"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::send_shortcut"
"[localization_language_index_]");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->mouse_control_button_label_)");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->audio_capture_button_label_)");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::select_file"
"[localization_language_index_]");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->net_traffic_stats_button_label_)");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->fullscreen_button_label_)");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::release_mouse"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::control_mouse"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::audio_capture"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::mute[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::hide_net_traffic_stats"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::show_net_traffic_stats"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::exit_fullscreen"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::fullscreen"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::disconnect"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::expand_control_bar"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::collapse_control_bar"
"[localization_language_index_]");
return ok ? 0 : 1; return ok ? 0 : 1;
} }