diff --git a/CMakeLists.txt b/CMakeLists.txt index fa105ed..8f0922b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -689,6 +689,7 @@ set_source_files_properties( "${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-R.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Light.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Medium.ttf;\ +${CMAKE_SOURCE_DIR}/res/fonts/UbuntuMono-R.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/MaterialIcons-Regular.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/NotoSansCJK-Subset.ttf" diff --git a/res/fonts/UbuntuMono-R.ttf b/res/fonts/UbuntuMono-R.ttf new file mode 100644 index 0000000..fdd309d Binary files /dev/null and b/res/fonts/UbuntuMono-R.ttf differ diff --git a/res/themes/ui.toml b/res/themes/ui.toml index b8412c0..e32726f 100644 --- a/res/themes/ui.toml +++ b/res/themes/ui.toml @@ -908,6 +908,7 @@ scroll-fade-zone = { size = 24.0 } [tabs.console] input-area-padding = 8.0 +bg-darken-alpha = { size = 110.0 } # black overlay alpha (0-255) for the terminal-dark output + input output-line-spacing = 2.0 output = { line-spacing = 2 } scroll-multiplier = { size = 3.0 } diff --git a/src/embedded/embedded_fonts.cpp.in b/src/embedded/embedded_fonts.cpp.in index 204998b..0871f76 100644 --- a/src/embedded/embedded_fonts.cpp.in +++ b/src/embedded/embedded_fonts.cpp.in @@ -11,6 +11,7 @@ INCBIN(ubuntu_regular, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-R.ttf"); INCBIN(ubuntu_light, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Light.ttf"); INCBIN(ubuntu_medium, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Medium.ttf"); +INCBIN(ubuntu_mono, "@CMAKE_SOURCE_DIR@/res/fonts/UbuntuMono-R.ttf"); INCBIN(material_icons, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialIcons-Regular.ttf"); INCBIN(mdi_pickaxe_subset, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf"); INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.ttf"); diff --git a/src/embedded/embedded_fonts.h b/src/embedded/embedded_fonts.h index 46ab61f..6b8b501 100644 --- a/src/embedded/embedded_fonts.h +++ b/src/embedded/embedded_fonts.h @@ -24,6 +24,9 @@ extern "C" { extern const unsigned char g_ubuntu_medium_data[]; extern const unsigned int g_ubuntu_medium_size; + extern const unsigned char g_ubuntu_mono_data[]; + extern const unsigned int g_ubuntu_mono_size; + extern const unsigned char g_material_icons_data[]; extern const unsigned int g_material_icons_size; diff --git a/src/ui/material/typography.cpp b/src/ui/material/typography.cpp index f19a6ae..681a682 100644 --- a/src/ui/material/typography.cpp +++ b/src/ui/material/typography.cpp @@ -183,7 +183,11 @@ bool Typography::load(ImGuiIO& io, float dpiScale) // ButtonLg: Medium fonts_[14] = loadFont(io, kWeightMedium, Layout::kFontButtonLg() * scale, "ButtonLg"); - + + // Monospace (Ubuntu Mono) at caption size — used by the console so terminal text + + // pretty-printed JSON columns align. Same size as Caption to keep line heights matched. + mono_ = loadFont(io, kWeightMono, Layout::kFontCaption() * scale, "Mono"); + // --- Icon fonts at multiple sizes --- // These are standalone fonts (not merged) so we can PushFont(iconXxx) for icon rendering. iconFonts_[0] = loadIconFont(io, 14.0f * scale, "IconSmall"); @@ -236,6 +240,10 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na fontData = g_ubuntu_medium_data; fontDataLen = g_ubuntu_medium_size; break; + case kWeightMono: + fontData = g_ubuntu_mono_data; + fontDataLen = g_ubuntu_mono_size; + break; case kWeightRegular: default: fontData = g_ubuntu_regular_data; @@ -271,10 +279,10 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na cfg.GlyphRanges = glyphRanges; // Create a unique name for this font variant - snprintf(cfg.Name, sizeof(cfg.Name), "Ubuntu %s %.0fpx", - weight == kWeightLight ? "Light" : - weight == kWeightMedium ? "Medium" : "Regular", - size); + const char* weightName = weight == kWeightLight ? "Light" : + weight == kWeightMedium ? "Medium" : + weight == kWeightMono ? "Mono" : "Regular"; + snprintf(cfg.Name, sizeof(cfg.Name), "Ubuntu %s %.0fpx", weightName, size); ImFont* font = io.Fonts->AddFontFromMemoryTTF(fontDataCopy, fontDataLen, size, &cfg); diff --git a/src/ui/material/typography.h b/src/ui/material/typography.h index f6de2be..50d09d4 100644 --- a/src/ui/material/typography.h +++ b/src/ui/material/typography.h @@ -168,6 +168,10 @@ public: ImFont* caption() const { return getFont(TypeStyle::Caption); } ImFont* overline() const { return getFont(TypeStyle::Overline); } + // Monospace (Ubuntu Mono) at caption size — for the console, so JSON/columns align + // like a real terminal. Falls back to the proportional caption font if not loaded. + ImFont* mono() const { return mono_ ? mono_ : caption(); } + // Icon fonts — Material Design Icons merged at specific pixel sizes // Use these when you need to render an icon at a specific size via AddText/ImGui::Text. // Common sizes: iconSmall (14px), iconMed (18px), iconLarge (24px). @@ -253,12 +257,16 @@ private: static constexpr int kWeightLight = 300; static constexpr int kWeightRegular = 400; static constexpr int kWeightMedium = 500; + static constexpr int kWeightMono = 401; // selects Ubuntu Mono (monospace, regular) bool loaded_ = false; float dpiScale_ = 1.0f; // Fonts for each type style ImFont* fonts_[15] = {}; + + // Monospace font (Ubuntu Mono) at caption size — console/terminal text. + ImFont* mono_ = nullptr; // Icon fonts at different sizes: [0]=small(14), [1]=med(18), [2]=large(24), [3]=xl(40) ImFont* iconFonts_[4] = {}; diff --git a/src/ui/windows/console_command_executor.h b/src/ui/windows/console_command_executor.h index 1cf640c..fd59f69 100644 --- a/src/ui/windows/console_command_executor.h +++ b/src/ui/windows/console_command_executor.h @@ -32,6 +32,17 @@ struct ConsoleStatusLine { bool pulse = false; // animate the toolbar dot (starting/connecting) }; +// Which log-filter toggles the toolbar should show for a given backend. The full node has a +// daemon/xmrig log + an RPC trace; the lite backend has neither (its diagnostics ring maps to +// the App/Error channels), so it only wants the errors-only + app-messages toggles. +struct ConsoleLogFilterCaps { + bool daemon = false; // daemon + xmrig output toggle + bool errorsOnly = false; // errors-only toggle + bool rpcTrace = false; // RPC method/source trace toggle + bool appMessages = false; // app / diagnostics log toggle + bool any() const { return daemon || errorsOnly || rpcTrace || appMessages; } +}; + class ConsoleCommandExecutor { public: virtual ~ConsoleCommandExecutor() = default; @@ -53,7 +64,8 @@ public: // UI-chrome capabilities. virtual bool hasRpcReference() const { return false; } // show the RPC command-reference popup - virtual bool hasLogFilters() const { return false; } // show daemon/errors/rpc-trace/app filters + // Which log-filter toggles the toolbar should show (default: none). + virtual ConsoleLogFilterCaps logFilterCaps() const { return {}; } // Print the backend-appropriate `help` output via `add`. virtual void printHelp(const ConsoleAddLineFn& add) = 0; @@ -75,7 +87,8 @@ public: bool pollResult(std::string& result, bool& isError) override; void pollLogLines(const ConsoleAddLineFn& add) override; bool hasRpcReference() const override { return true; } - bool hasLogFilters() const override { return true; } + // Full node: daemon/xmrig log, errors-only, RPC trace, and app messages. + ConsoleLogFilterCaps logFilterCaps() const override { return {true, true, true, true}; } void printHelp(const ConsoleAddLineFn& add) override; ConsoleStatusLine toolbarStatus() const override; @@ -99,6 +112,9 @@ public: void submit(const std::string& cmd) override; bool pollResult(std::string& result, bool& isError) override; void pollLogLines(const ConsoleAddLineFn& add) override; + // Lite: no daemon log / RPC trace — its diagnostics ring maps to the App + Error + // channels, so offer errors-only + app-messages (plus the always-shown text filter). + ConsoleLogFilterCaps logFilterCaps() const override { return {false, true, false, true}; } void printHelp(const ConsoleAddLineFn& add) override; std::vector statusLines() const override; ConsoleStatusLine toolbarStatus() const override; diff --git a/src/ui/windows/console_tab.cpp b/src/ui/windows/console_tab.cpp index 56538ea..d594354 100644 --- a/src/ui/windows/console_tab.cpp +++ b/src/ui/windows/console_tab.cpp @@ -151,6 +151,23 @@ ImU32 ConsoleTab::channelTextColor(ConsoleChannel channel) const } } +ImU32 ConsoleTab::channelAccentColor(ConsoleChannel channel) +{ + using namespace material; + switch (channel) { + case ConsoleChannel::Command: return Primary(); + case ConsoleChannel::Error: return Error(); + case ConsoleChannel::Warning: return Warning(); + case ConsoleChannel::Success: return Success(); + case ConsoleChannel::Rpc: return Secondary(); + case ConsoleChannel::Daemon: return IM_COL32(90, 130, 190, 210); // node log — blue + case ConsoleChannel::Xmrig: return Warning(); // mining — amber + case ConsoleChannel::App: return IM_COL32(120, 190, 160, 210); // app — teal + case ConsoleChannel::Info: return OnSurfaceDisabled(); + default: return 0; // result / JSON roles get no accent + } +} + ConsoleTab::ConsoleTab() { { @@ -246,6 +263,11 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec) outGlass.rounding = Layout::glassRounding(); outGlass.fillAlpha = 12; DrawGlassPanel(dlOut, outPanelMin, outPanelMax, outGlass); + // Terminal-dark overlay: dim the glass so console text reads like a real terminal. + { + int darkA = static_cast(schema::UI().drawElement("tabs.console", "bg-darken-alpha").sizeOr(110.0f)); + dlOut->AddRectFilled(outPanelMin, outPanelMax, IM_COL32(0, 0, 0, darkA), outGlass.rounding); + } int consoleParentVtx = dlOut->VtxBuffer.Size; @@ -256,8 +278,8 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec) int consoleChildVtx = consoleChildDL->VtxBuffer.Size; float consoleScrollY = ImGui::GetScrollY(); float consoleScrollMaxY = ImGui::GetScrollMaxY(); - // Use smaller font for console output - ImGui::PushFont(Type().caption()); + // Console output uses the monospace font so terminal text + JSON columns align. + ImGui::PushFont(Type().mono()); ImGui::SetWindowFontScale(s_console_zoom); renderOutput(); ImGui::SetWindowFontScale(1.0f); @@ -270,7 +292,7 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec) // away from the bottom before we check position again. scroll_.tickCooldown(ImGui::GetIO().DeltaTime); { - float tolerance = Type().caption()->LegacySize * 1.5f; + float tolerance = Type().mono()->LegacySize * 1.5f; bool atBottom = consoleScrollMaxY > 0.0f && consoleScrollY >= consoleScrollMaxY - tolerance; scroll_.considerReenable(atBottom); @@ -280,7 +302,7 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec) // When auto-scroll is off, force bottom fade to always show by // inflating scrollMax so the mask thinks there's content below. { - float fadeZone = std::min(Type().caption()->LegacySize * 3.0f, outputH * 0.18f); + float fadeZone = std::min(Type().mono()->LegacySize * 3.0f, outputH * 0.18f); float effectiveScrollMax = scroll_.autoScroll() ? consoleScrollMaxY : std::max(consoleScrollMaxY, consoleScrollY + 10.0f); ApplyScrollEdgeMask(dlOut, consoleParentVtx, consoleChildDL, consoleChildVtx, @@ -290,7 +312,7 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec) // CRT scanline effect over output area — aligned to text lines if (s_scanline_enabled) { float textLineH = output_line_height_; - if (textLineH <= 1.0f) textLineH = Type().caption()->LegacySize * s_console_zoom + 2.0f; + if (textLineH <= 1.0f) textLineH = Type().mono()->LegacySize * s_console_zoom + 2.0f; int lightAlpha = std::clamp((int)schema::UI().drawElement("tabs.console", "scanline-line-alpha").sizeOr(10.0f), 0, 255); int darkAlpha = std::clamp(lightAlpha + 10, 0, 255); @@ -382,9 +404,11 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec) ImGui::Spacing(); ImGui::SameLine(); - // Log filters (daemon/errors/rpc-trace/app) apply only to a daemon log source. - if (exec.hasLogFilters()) { - drawLogFilterToggles(); + // Log-filter toggles — only the ones the backend supports (full node = all; lite = + // errors-only + app), each tinted with its channel color. + ConsoleLogFilterCaps filterCaps = exec.logFilterCaps(); + if (filterCaps.any()) { + drawLogFilterToggles(filterCaps); } // Clear button @@ -474,49 +498,51 @@ void ConsoleTab::drawToolbarStatus(ConsoleCommandExecutor& exec) } } -void ConsoleTab::drawLogFilterToggles() +void ConsoleTab::drawLogFilterToggles(const ConsoleLogFilterCaps& caps) { - // Daemon messages toggle - ImGui::Checkbox(TR("console_daemon"), &s_daemon_messages_enabled); - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_daemon_output")); + // Each toggle is tinted with its channel's accent color (the same color as that channel's + // left accent bar in the output), so the filter maps visually to the lines it controls. + // Only the toggles the backend supports (caps) are shown — lite has no daemon/rpc-trace. + bool drawnAny = false; + auto separate = [&drawnAny]() { + if (drawnAny) { ImGui::SameLine(); ImGui::Spacing(); ImGui::SameLine(); } + drawnAny = true; + }; + auto colored = [](ConsoleChannel ch, const char* label, bool* v, const char* tip) -> bool { + ImVec4 c = ImGui::ColorConvertU32ToFloat4(channelAccentColor(ch)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, c); + ImGui::PushStyleColor(ImGuiCol_Text, c); + bool changed = ImGui::Checkbox(label, v); + ImGui::PopStyleColor(2); + if (ImGui::IsItemHovered()) material::Tooltip("%s", tip); + return changed; + }; + + if (caps.daemon) { + separate(); + colored(ConsoleChannel::Daemon, TR("console_daemon"), &s_daemon_messages_enabled, + TR("console_show_daemon_output")); + } + if (caps.errorsOnly) { + separate(); + colored(ConsoleChannel::Error, TR("console_errors"), &s_errors_only_enabled, + TR("console_show_errors_only")); + } + if (caps.rpcTrace) { + separate(); + // Captures method/source only, never results or params. + if (colored(ConsoleChannel::Rpc, TR("console_rpc_trace"), &s_rpc_trace_enabled, + TR("console_show_rpc_trace"))) + rpc::RPCClient::setTraceEnabled(s_rpc_trace_enabled); + } + if (caps.appMessages) { + separate(); + colored(ConsoleChannel::App, TR("console_app"), &s_app_messages_enabled, + TR("console_show_app_output")); } - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - // Errors-only toggle - ImGui::Checkbox(TR("console_errors"), &s_errors_only_enabled); - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_errors_only")); - } - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - // App RPC trace toggle — captures method/source only, never results or params - if (ImGui::Checkbox(TR("console_rpc_trace"), &s_rpc_trace_enabled)) { - rpc::RPCClient::setTraceEnabled(s_rpc_trace_enabled); - } - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_rpc_trace")); - } - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - // App messages toggle — "[app] ..." wallet log lines - ImGui::Checkbox(TR("console_app"), &s_app_messages_enabled); - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_app_output")); - } - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); + // Trailing spacer so the next toolbar item (Clear) stays on the same line. + if (drawnAny) { ImGui::SameLine(); ImGui::Spacing(); ImGui::SameLine(); } } void ConsoleTab::drawFilterInput() @@ -657,7 +683,7 @@ void ConsoleTab::renderOutput() // toggles — otherwise the geometry shift clamps the user back to // bottom and a single scroll-up tick can't escape. { - float fadeZone = std::min(Type().caption()->LegacySize * 3.0f, + float fadeZone = std::min(Type().mono()->LegacySize * 3.0f, ImGui::GetWindowHeight() * 0.18f); ImGui::Dummy(ImVec2(0, fadeZone)); } @@ -825,21 +851,9 @@ void ConsoleTab::drawVisibleLines(float padX, float lineHeight, bool hasTextFilt float totalH = layout_.heights[vi]; // Left-edge channel accent bar, drawn in the padX margin so it never overlaps - // the text or the selection highlight. + // the text or the selection highlight. Same color that tints the toolbar toggle. if (line.channel != ConsoleChannel::None) { - ImU32 barCol = 0; - switch (line.channel) { - case ConsoleChannel::Command: barCol = Primary(); break; - case ConsoleChannel::Error: barCol = Error(); break; - case ConsoleChannel::Warning: barCol = Warning(); break; - case ConsoleChannel::Success: barCol = Success(); break; - case ConsoleChannel::Rpc: barCol = Secondary(); break; - case ConsoleChannel::Daemon: barCol = IM_COL32(90, 130, 190, 210); break; // node log — blue - case ConsoleChannel::Xmrig: barCol = Warning(); break; // mining — amber - case ConsoleChannel::App: barCol = IM_COL32(120, 190, 160, 210); break; // app — teal - case ConsoleChannel::Info: barCol = OnSurfaceDisabled(); break; - default: break; // result / JSON roles get no bar - } + ImU32 barCol = channelAccentColor(line.channel); if (barCol != 0) { float barW = 3.0f * Layout::hScale(); float barX = output_origin_.x - padX + 3.0f * Layout::hScale(); @@ -1109,10 +1123,12 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec) float inputPanelH = ImGui::GetFrameHeightWithSpacing() + Layout::spacingSm() + Layout::spacingXs(); ImVec2 inMin = ImGui::GetCursorScreenPos(); ImVec2 inMax(inMin.x + ImGui::GetContentRegionAvail().x, inMin.y + inputPanelH); - GlassPanelSpec inGlass; - inGlass.rounding = Layout::glassRounding(); - inGlass.fillAlpha = 12; - material::DrawGlassPanel(dlIn, inMin, inMax, inGlass); + // No light glass rectangle on the input — a flat terminal-dark bar instead, matching + // the darkened output panel above. + { + int darkA = static_cast(schema::UI().drawElement("tabs.console", "bg-darken-alpha").sizeOr(110.0f)); + dlIn->AddRectFilled(inMin, inMax, IM_COL32(0, 0, 0, darkA), Layout::glassRounding()); + } // Center content vertically within glass panel float inputFrameH = ImGui::GetFrameHeight(); @@ -1121,9 +1137,10 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec) - // Input field + // Input field — monospace to match the terminal-style output. + ImGui::PushFont(Type().mono()); ImGui::PushItemWidth(-Layout::spacingMd()); - + ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCompletion; @@ -1177,6 +1194,11 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec) return 0; }; + // Blend the field into the dark terminal bar: transparent frame, subtle hover/active. + ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(255, 255, 255, 12)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, IM_COL32(255, 255, 255, 18)); + const bool busy = exec.busy(); if (busy) ImGui::BeginDisabled(); if (ImGui::InputText("##ConsoleInput", input_buffer_, sizeof(input_buffer_), flags, callback, this)) { @@ -1192,8 +1214,10 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec) } } if (busy) ImGui::EndDisabled(); + ImGui::PopStyleColor(3); ImGui::PopItemWidth(); + ImGui::PopFont(); // Auto-focus on input if (reclaim_focus) { diff --git a/src/ui/windows/console_tab.h b/src/ui/windows/console_tab.h index 6d449b3..b13f9fc 100644 --- a/src/ui/windows/console_tab.h +++ b/src/ui/windows/console_tab.h @@ -24,6 +24,7 @@ namespace dragonx { namespace ui { class ConsoleCommandExecutor; +struct ConsoleLogFilterCaps; /** * @brief Console tab — a rich terminal shared by the full-node and lite variants. @@ -92,6 +93,9 @@ private: // line stores only its semantic ConsoleChannel (in ConsoleModel); the text color and // the left accent-bar color are both derived from it at draw time. ImU32 channelTextColor(ConsoleChannel channel) const; + // Saturated accent color for a channel — the left accent-bar color, also used to tint the + // matching toolbar filter toggle. 0 for channels that get no accent (result/JSON/None). + static ImU32 channelAccentColor(ConsoleChannel channel); // Drain the thread-safe ingest queue into the visible model (once per frame, main // thread), applying the line cap + selection/scroll bookkeeping for the new lines. @@ -105,7 +109,7 @@ private: // renderToolbar() draws the top bar; these are its sub-steps: void renderToolbar(ConsoleCommandExecutor& exec); void drawToolbarStatus(ConsoleCommandExecutor& exec); // backend status dot + label - void drawLogFilterToggles(); // daemon/errors/rpc/app checkboxes + void drawLogFilterToggles(const ConsoleLogFilterCaps& caps); // color-coded filter checkboxes void drawFilterInput(); // text filter + match count void drawZoomControls(); // zoom -/+ buttons