mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 03:11:47 +00:00
Testing macOS fixes
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
// - action.input "percent"/"tab" -> NativeCommands catalog (phases handled in activateEntry)
|
// - action.input "percent"/"tab" -> NativeCommands catalog (phases handled in activateEntry)
|
||||||
// - action.icon SVG base name -> AppAction::icon / resources/images/<name>.svg
|
// - action.icon SVG base name -> AppAction::icon / resources/images/<name>.svg
|
||||||
// - action.desc/wiki -> AppAction::tooltip / help_url (footer detail strip)
|
// - action.desc/wiki -> AppAction::tooltip / help_url (footer detail strip)
|
||||||
|
// - payload.tooltip_expanded -> ActionRegistry::tooltip_expanded (persisted footer state)
|
||||||
// - action list is frecency-sorted -> ActionRegistry::snapshot()
|
// - action list is frecency-sorted -> ActionRegistry::snapshot()
|
||||||
|
|
||||||
// ---- state (populated by the C++ bridge via window.HandleStudio) ----
|
// ---- state (populated by the C++ bridge via window.HandleStudio) ----
|
||||||
@@ -17,9 +18,12 @@ var FAVS = []; // [id...]
|
|||||||
var RECENTS = []; // [{id,title,source,group,kind,input,icon,mode}] - last-N launched
|
var RECENTS = []; // [{id,title,source,group,kind,input,icon,mode}] - last-N launched
|
||||||
var query = "";
|
var query = "";
|
||||||
var sel = { zone: "list", i: 0 }; // zone: 'list' | 'fav'
|
var sel = { zone: "list", i: 0 }; // zone: 'list' | 'fav'
|
||||||
var lastResizeHeight = 0;
|
|
||||||
var matchIndex = {};
|
var matchIndex = {};
|
||||||
|
|
||||||
|
// Global tooltip expansion, seeded from C++ (persisted in the speed_dial config section). Collapsing
|
||||||
|
// hides the footer description + wiki link for every action; the arrow remains to expand again.
|
||||||
|
var TOOLTIP_EXPANDED = true;
|
||||||
|
|
||||||
// The user's current settings mode (from the C++ payload) plus the rank order of the modes. Each
|
// The user's current settings mode (from the C++ payload) plus the rank order of the modes. Each
|
||||||
// action carries the mode it requires, so "would this need a switch?" is a rank comparison.
|
// action carries the mode it requires, so "would this need a switch?" is a rank comparison.
|
||||||
var USER_MODE = "simple";
|
var USER_MODE = "simple";
|
||||||
@@ -548,6 +552,10 @@ function actionHasWiki(a) { return !!(a && a.wiki); }
|
|||||||
// Whether the action has anything for the footer strip to show (a description or a wiki link).
|
// Whether the action has anything for the footer strip to show (a description or a wiki link).
|
||||||
function actionHasDetail(a) { return !!(a && ((a.desc && a.desc.length) || a.wiki)); }
|
function actionHasDetail(a) { return !!(a && ((a.desc && a.desc.length) || a.wiki)); }
|
||||||
|
|
||||||
|
// The expand/collapse arrow is offered for actions that carry a description. Collapsing is a global
|
||||||
|
// (persisted) preference, so even a short tooltip gets the control.
|
||||||
|
function detailToggleVisible(a) { return !!(a && a.desc && a.desc.length); }
|
||||||
|
|
||||||
function foldLabel(s) { return String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, ""); }
|
function foldLabel(s) { return String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, ""); }
|
||||||
|
|
||||||
// Title-case a source for display: "GCODE OPTIMIZER"/"iRoNiNg pRo" -> "Gcode Optimizer"/"Ironing Pro".
|
// Title-case a source for display: "GCODE OPTIMIZER"/"iRoNiNg pRo" -> "Gcode Optimizer"/"Ironing Pro".
|
||||||
@@ -634,7 +642,8 @@ function stateFromPayload(payload) {
|
|||||||
actions: payload.actions || [],
|
actions: payload.actions || [],
|
||||||
favourites: payload.favourites || [],
|
favourites: payload.favourites || [],
|
||||||
recent: payload.recent || [],
|
recent: payload.recent || [],
|
||||||
userMode: payload.user_mode || "simple"
|
userMode: payload.user_mode || "simple",
|
||||||
|
tooltipExpanded: payload.tooltip_expanded !== false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,12 +699,12 @@ window.HandleStudio = function (payload) {
|
|||||||
FAVS = next.favourites;
|
FAVS = next.favourites;
|
||||||
RECENTS = next.recent;
|
RECENTS = next.recent;
|
||||||
USER_MODE = next.userMode;
|
USER_MODE = next.userMode;
|
||||||
|
TOOLTIP_EXPANDED = next.tooltipExpanded;
|
||||||
// A fresh payload re-opens the main phase; C++ never rehydrates the transient phase/query state.
|
// A fresh payload re-opens the main phase; C++ never rehydrates the transient phase/query state.
|
||||||
phase = "commands";
|
phase = "commands";
|
||||||
tabOptions = [];
|
tabOptions = [];
|
||||||
query = "";
|
query = "";
|
||||||
sel = { zone: "list", i: 0 };
|
sel = { zone: "list", i: 0 };
|
||||||
lastResizeHeight = 0;
|
|
||||||
// why: builtKey caches phase|query so renderCommandsList can skip a rebuild on arrow-nav.
|
// why: builtKey caches phase|query so renderCommandsList can skip a rebuild on arrow-nav.
|
||||||
// It survives a re-open (which never goes through exitPhase), so without a reset the cached
|
// It survives a re-open (which never goes through exitPhase), so without a reset the cached
|
||||||
// empty-query key would skip the rebuild and leave stale list content.
|
// empty-query key would skip the rebuild and leave stale list content.
|
||||||
@@ -1153,23 +1162,26 @@ function currentDetailAction() {
|
|||||||
return id ? byId(id) : null;
|
return id ? byId(id) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer detail strip: the selected action's description plus, when it has a wiki page, a link that
|
// Footer detail strip: the selected action's description, its wiki link and the expand/collapse
|
||||||
// opens it (same path as F1). Shown only when the highlighted action has something to say, so
|
// arrow. Shown whenever the highlighted action has a description or a wiki page; expanding is a
|
||||||
// selecting a command with no description hides the strip.
|
// persisted global preference, so the arrow stays available to collapse/expand every tooltip.
|
||||||
function renderDetail() {
|
function renderDetail() {
|
||||||
if (!detailEl) return;
|
if (!detailEl) return;
|
||||||
var a = currentDetailAction();
|
var a = currentDetailAction();
|
||||||
|
var hasDesc = detailToggleVisible(a);
|
||||||
var show = phase === "commands" && actionHasDetail(a);
|
var show = phase === "commands" && actionHasDetail(a);
|
||||||
detailEl.hidden = !show;
|
detailEl.hidden = !show;
|
||||||
detailEl.innerHTML = "";
|
detailEl.innerHTML = "";
|
||||||
if (!show) return;
|
if (!show) return;
|
||||||
if (a && a.desc) {
|
if (hasDesc && TOOLTIP_EXPANDED) {
|
||||||
var desc = document.createElement("div");
|
var desc = document.createElement("div");
|
||||||
desc.className = "detail-desc";
|
desc.className = "detail-desc";
|
||||||
desc.textContent = a.desc;
|
desc.textContent = a.desc;
|
||||||
detailEl.appendChild(desc);
|
detailEl.appendChild(desc);
|
||||||
}
|
}
|
||||||
if (a && a.wiki) {
|
// The wiki link is part of the expanded detail, so collapsing hides it too. An action with only a
|
||||||
|
// wiki (no description) has nothing to collapse, so its link always shows.
|
||||||
|
if (a.wiki && (!hasDesc || TOOLTIP_EXPANDED)) {
|
||||||
var link = document.createElement("button");
|
var link = document.createElement("button");
|
||||||
link.type = "button";
|
link.type = "button";
|
||||||
link.className = "detail-wiki";
|
link.className = "detail-wiki";
|
||||||
@@ -1177,6 +1189,25 @@ function renderDetail() {
|
|||||||
link.onclick = function (ev) { ev.stopPropagation(); SendMessage({ command: "open_wiki", id: a.id }); };
|
link.onclick = function (ev) { ev.stopPropagation(); SendMessage({ command: "open_wiki", id: a.id }); };
|
||||||
detailEl.appendChild(link);
|
detailEl.appendChild(link);
|
||||||
}
|
}
|
||||||
|
if (hasDesc) {
|
||||||
|
var toggle = document.createElement("button");
|
||||||
|
toggle.type = "button";
|
||||||
|
toggle.className = "detail-toggle";
|
||||||
|
toggle.setAttribute("aria-expanded", TOOLTIP_EXPANDED ? "true" : "false");
|
||||||
|
var label = TOOLTIP_EXPANDED ? T("sd_hide_details", "Hide details") : T("sd_show_details", "Show details");
|
||||||
|
toggle.title = label;
|
||||||
|
toggle.setAttribute("aria-label", label);
|
||||||
|
toggle.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" ' +
|
||||||
|
'stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
||||||
|
'<polyline points="4,6 8,10 12,6"/></svg>';
|
||||||
|
toggle.onclick = function (ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
TOOLTIP_EXPANDED = !TOOLTIP_EXPANDED;
|
||||||
|
SendMessage({ command: "set_tooltip_expanded", expanded: TOOLTIP_EXPANDED });
|
||||||
|
render({ resize: true });
|
||||||
|
};
|
||||||
|
detailEl.appendChild(toggle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the muted inline completion shown at the end of the search field. Only offered in the
|
// Refresh the muted inline completion shown at the end of the search field. Only offered in the
|
||||||
@@ -1230,10 +1261,14 @@ function requestResize() {
|
|||||||
var launcher = document.querySelector(".launcher");
|
var launcher = document.querySelector(".launcher");
|
||||||
if (!launcher)
|
if (!launcher)
|
||||||
return;
|
return;
|
||||||
var height = Math.ceil(launcher.getBoundingClientRect().height);
|
// Include any overflow (WebKit can report a -webkit-box border box a fraction short of its
|
||||||
if (!height || height === lastResizeHeight)
|
// content), so the window never clips the footer's last line.
|
||||||
|
var height = Math.ceil(Math.max(launcher.getBoundingClientRect().height, launcher.scrollHeight));
|
||||||
|
if (!height)
|
||||||
return;
|
return;
|
||||||
lastResizeHeight = height;
|
// Always (re)send rather than caching: a resize can be measured but dropped (e.g. while the
|
||||||
|
// window is being shown) and an unchanged-size cache would then suppress every retry. C++
|
||||||
|
// SetClientSize is a no-op on an unchanged size, so this is cheap.
|
||||||
SendMessage({ command: "resize", height: height });
|
SendMessage({ command: "resize", height: height });
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
@@ -1448,7 +1483,7 @@ function OnInit() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sel = nextSel(sel, e.key, list.length, favs.length);
|
sel = nextSel(sel, e.key, list.length, favs.length);
|
||||||
// why: entering/leaving the fav zone toggles the eyebrow line, changing launcher height;
|
// why: entering/leaving the fav zone toggles the eyebrow line, changing launcher height;
|
||||||
// resize so the popup grows/shrinks instead of clipping. requestResize no-ops when unchanged.
|
// resize so the popup grows/shrinks instead of clipping.
|
||||||
render({ resize: true });
|
render({ resize: true });
|
||||||
} else if (e.key === "Enter") {
|
} else if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1462,5 +1497,15 @@ function OnInit() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keep the dialog sized to the content: any reflow that lands after a render (tooltip
|
||||||
|
// expand/collapse or clamped-box settling, font metrics, list reveal) re-measures. Without this
|
||||||
|
// a later reflow left the window a few pixels short and clipped the footer's last line.
|
||||||
|
if (typeof ResizeObserver !== "undefined") {
|
||||||
|
new ResizeObserver(function () { requestResize(); }).observe(document.querySelector(".launcher"));
|
||||||
|
}
|
||||||
|
// Font metrics can swap after first layout; re-measure once they settle.
|
||||||
|
if (document.fonts && document.fonts.ready && document.fonts.ready.then)
|
||||||
|
document.fonts.ready.then(function () { requestResize(); });
|
||||||
|
|
||||||
SendMessage({ command: "request_actions" });
|
SendMessage({ command: "request_actions" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,4 +422,18 @@ assert.equal(ctx.actionHasDetail({ id: "a", desc: "", wiki: false }), false, "em
|
|||||||
assert.equal(ctx.actionHasDetail({ id: "a" }), false, "an action with neither hides the footer");
|
assert.equal(ctx.actionHasDetail({ id: "a" }), false, "an action with neither hides the footer");
|
||||||
assert.equal(ctx.actionHasDetail(null), false, "no selected action hides the footer");
|
assert.equal(ctx.actionHasDetail(null), false, "no selected action hides the footer");
|
||||||
|
|
||||||
|
// detailToggleVisible: the expand/collapse arrow is offered only when there is a description to
|
||||||
|
// toggle. Collapse is a global preference, so the control stays for short tooltips too.
|
||||||
|
assert.equal(ctx.detailToggleVisible({ id: "a", desc: "Layer height" }), true, "a description offers the toggle");
|
||||||
|
assert.equal(ctx.detailToggleVisible({ id: "a", desc: "" }), false, "an empty description offers no toggle");
|
||||||
|
assert.equal(ctx.detailToggleVisible({ id: "a", wiki: true }), false, "a wiki-only action has nothing to collapse");
|
||||||
|
assert.equal(ctx.detailToggleVisible({ id: "a" }), false, "an action with no description offers no toggle");
|
||||||
|
assert.equal(ctx.detailToggleVisible(null), false, "no selected action offers no toggle");
|
||||||
|
|
||||||
|
// stateFromPayload: the footer expansion is a persisted global and defaults to expanded when the
|
||||||
|
// C++ payload omits it (first run / older config).
|
||||||
|
assert.equal(ctx.stateFromPayload({}).tooltipExpanded, true, "expansion defaults to true when absent");
|
||||||
|
assert.equal(ctx.stateFromPayload({ tooltip_expanded: false }).tooltipExpanded, false, "a collapsed payload is honored");
|
||||||
|
assert.equal(ctx.stateFromPayload({ tooltip_expanded: true }).tooltipExpanded, true, "an expanded payload is honored");
|
||||||
|
|
||||||
console.log("ok");
|
console.log("ok");
|
||||||
|
|||||||
@@ -476,8 +476,8 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer detail strip: the selected action's description plus its wiki link. Auto-sizes to the
|
/* Footer detail strip: the selected action's description, its wiki link and the expand/collapse
|
||||||
content; the description is clamped below. */
|
arrow. Auto-sizes to the content; the description is clamped to 6 lines. */
|
||||||
.dial-detail {
|
.dial-detail {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -500,7 +500,7 @@ body {
|
|||||||
color: var(--muted, var(--orca-muted, #6b7280));
|
color: var(--muted, var(--orca-muted, #6b7280));
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 6;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
@@ -520,3 +520,30 @@ body {
|
|||||||
.detail-wiki:hover {
|
.detail-wiki:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Expand/collapse arrow for the tooltip. Pinned right so it stays put whether or not the
|
||||||
|
description/wiki are visible; the chevron points up (collapse) when expanded. */
|
||||||
|
.detail-toggle {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-left: auto;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 0;
|
||||||
|
padding: 2px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--muted, var(--orca-muted, #6b7280));
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-toggle:hover {
|
||||||
|
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-toggle svg {
|
||||||
|
transition: transform .12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-toggle[aria-expanded="true"] svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -726,6 +726,19 @@ void ActionRegistry::suppress_ask(const std::string& id)
|
|||||||
write_section("ask_suppressed", nlohmann::json(arr));
|
write_section("ask_suppressed", nlohmann::json(arr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ActionRegistry::tooltip_expanded() const
|
||||||
|
{
|
||||||
|
assert(wxThread::IsMain());
|
||||||
|
const nlohmann::json j = read_section("tooltip_expanded", nlohmann::json(true));
|
||||||
|
return j.is_boolean() ? j.get<bool>() : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionRegistry::set_tooltip_expanded(bool expanded)
|
||||||
|
{
|
||||||
|
assert(wxThread::IsMain());
|
||||||
|
write_section("tooltip_expanded", nlohmann::json(expanded));
|
||||||
|
}
|
||||||
|
|
||||||
// ---- snapshot ---------------------------------------------------------------
|
// ---- snapshot ---------------------------------------------------------------
|
||||||
|
|
||||||
nlohmann::json ActionRegistry::snapshot()
|
nlohmann::json ActionRegistry::snapshot()
|
||||||
@@ -810,7 +823,8 @@ nlohmann::json ActionRegistry::snapshot()
|
|||||||
return {{"actions", std::move(actions)},
|
return {{"actions", std::move(actions)},
|
||||||
{"favourites", std::move(favourites)},
|
{"favourites", std::move(favourites)},
|
||||||
{"recent", std::move(recent_json)},
|
{"recent", std::move(recent_json)},
|
||||||
{"user_mode", mode_key(wxGetApp().get_mode())}};
|
{"user_mode", mode_key(wxGetApp().get_mode())},
|
||||||
|
{"tooltip_expanded", tooltip_expanded()}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- tab options (enumerate the MainFrame notebook's current pages) ----------
|
// ---- tab options (enumerate the MainFrame notebook's current pages) ----------
|
||||||
|
|||||||
@@ -196,6 +196,11 @@ public:
|
|||||||
bool should_ask(const std::string& id) const;
|
bool should_ask(const std::string& id) const;
|
||||||
void suppress_ask(const std::string& id);
|
void suppress_ask(const std::string& id);
|
||||||
|
|
||||||
|
// Footer expand/collapse preference. Global (applies to every action) and persisted; absent
|
||||||
|
// means expanded, so a fresh config picks the richer default with no migration.
|
||||||
|
bool tooltip_expanded() const;
|
||||||
|
void set_tooltip_expanded(bool expanded);
|
||||||
|
|
||||||
// Flat, frecency-sorted snapshot for the webview:
|
// Flat, frecency-sorted snapshot for the webview:
|
||||||
// {actions:[...], favourites:[...], recent:[...]} (recent = last-N launched by recency).
|
// {actions:[...], favourites:[...], recent:[...]} (recent = last-N launched by recency).
|
||||||
nlohmann::json snapshot();
|
nlohmann::json snapshot();
|
||||||
|
|||||||
@@ -478,6 +478,8 @@ int get_dpi_for_window(const wxWindow *window);
|
|||||||
#ifdef __WXOSX__
|
#ifdef __WXOSX__
|
||||||
void dataview_remove_insets(wxDataViewCtrl* dv);
|
void dataview_remove_insets(wxDataViewCtrl* dv);
|
||||||
void staticbox_remove_margin(wxStaticBox* sb);
|
void staticbox_remove_margin(wxStaticBox* sb);
|
||||||
|
// Clip a top-level window (and its webview) to a rounded rect with a native layer.
|
||||||
|
void set_window_corner_radius(wxWindow* win, int radius);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __WXGTK__
|
#ifdef __WXGTK__
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
#import <wx/osx/cocoa/dataview.h>
|
#import <wx/osx/cocoa/dataview.h>
|
||||||
#import "GUI_Utils.hpp"
|
#import "GUI_Utils.hpp"
|
||||||
|
|
||||||
@@ -21,6 +22,22 @@ void staticbox_remove_margin(wxStaticBox* sb) {
|
|||||||
[nativeBox setBorderWidth:0];
|
[nativeBox setBorderWidth:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wxOSX SetShape only clears the window background; it cannot clip to a region. Clipping the
|
||||||
|
// window's view layer to a rounded rect is what actually rounds the opaque webview inside.
|
||||||
|
void set_window_corner_radius(wxWindow* win, int radius) {
|
||||||
|
if (!win)
|
||||||
|
return;
|
||||||
|
NSView* view = (NSView*)win->GetHandle();
|
||||||
|
if (!view)
|
||||||
|
return;
|
||||||
|
NSWindow* window = [view window];
|
||||||
|
[window setOpaque:NO];
|
||||||
|
[window setBackgroundColor:[NSColor clearColor]];
|
||||||
|
[view setWantsLayer:YES];
|
||||||
|
[[view layer] setCornerRadius:radius];
|
||||||
|
[[view layer] setMasksToBounds:YES];
|
||||||
|
}
|
||||||
|
|
||||||
bool is_debugger_present()
|
bool is_debugger_present()
|
||||||
// Returns true if the current process is being debugged (either
|
// Returns true if the current process is being debugged (either
|
||||||
// running under the debugger or has a debugger attached post facto).
|
// running under the debugger or has a debugger attached post facto).
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ nlohmann::json speed_dial_ui_strings()
|
|||||||
{"sd_mode_develop", _u8L("Developer")},
|
{"sd_mode_develop", _u8L("Developer")},
|
||||||
{"sd_wiki_f1", _u8L("Wiki (F1)")},
|
{"sd_wiki_f1", _u8L("Wiki (F1)")},
|
||||||
{"sd_no_wiki", _u8L("No wiki page for this action")},
|
{"sd_no_wiki", _u8L("No wiki page for this action")},
|
||||||
|
{"sd_show_details", _u8L("Show details")},
|
||||||
|
{"sd_hide_details", _u8L("Hide details")},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +150,8 @@ SpeedDialWebDialog::SpeedDialWebDialog(wxWindow* parent)
|
|||||||
// the page inside the fixed-size popup. No-op on the other backends (wxWidgets 3.3 base virtual).
|
// the page inside the fixed-size popup. No-op on the other backends (wxWidgets 3.3 base virtual).
|
||||||
if (wxWebView* wv = browser())
|
if (wxWebView* wv = browser())
|
||||||
wv->EnableBrowserAcceleratorKeys(false);
|
wv->EnableBrowserAcceleratorKeys(false);
|
||||||
// Re-cut the shape region whenever layout changes the client size; SetShape itself
|
// Re-cut the shape whenever layout changes the client size. wxOSX SetShape resizes the
|
||||||
// does not generate size events, so this cannot recurse.
|
// NSWindow, which fires this synchronously; apply_rounded_shape() guards re-entry.
|
||||||
Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
|
Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
|
||||||
event.Skip();
|
event.Skip();
|
||||||
apply_rounded_shape();
|
apply_rounded_shape();
|
||||||
@@ -224,7 +226,9 @@ void SpeedDialWebDialog::handle_web_command(const nlohmann::json& payload)
|
|||||||
if (id.is_string())
|
if (id.is_string())
|
||||||
ids.push_back(id.get<std::string>());
|
ids.push_back(id.get<std::string>());
|
||||||
wxGetApp().action_registry().reorder_favourites(ids);
|
wxGetApp().action_registry().reorder_favourites(ids);
|
||||||
} else if (command == "run_action")
|
} else if (command == "set_tooltip_expanded")
|
||||||
|
wxGetApp().action_registry().set_tooltip_expanded(payload.value("expanded", true));
|
||||||
|
else if (command == "run_action")
|
||||||
run_action(payload.value("id", ""), payload.value("title", ""), payload.value("param", ""));
|
run_action(payload.value("id", ""), payload.value("title", ""), payload.value("param", ""));
|
||||||
else if (command == "open_wiki")
|
else if (command == "open_wiki")
|
||||||
open_wiki(payload.value("id", ""));
|
open_wiki(payload.value("id", ""));
|
||||||
@@ -259,22 +263,42 @@ void SpeedDialWebDialog::resize_to_content(int height)
|
|||||||
const int height_dip = std::max(kPopupMinHeight, std::min(height, max_dip));
|
const int height_dip = std::max(kPopupMinHeight, std::min(height, max_dip));
|
||||||
SetClientSize(FromDIP(wxSize(kPopupWidth, height_dip)));
|
SetClientSize(FromDIP(wxSize(kPopupWidth, height_dip)));
|
||||||
Layout();
|
Layout();
|
||||||
|
#ifdef __WXOSX__
|
||||||
|
// WKWebView can lag the dialog's new client size; force the viewport to match so the page is
|
||||||
|
// never painted (and clipped by the rounded layer) below the footer.
|
||||||
|
if (wxWebView* wv = browser()) {
|
||||||
|
const wxSize client = GetClientSize();
|
||||||
|
if (wv->GetSize() != client)
|
||||||
|
wv->SetSize(client);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
apply_rounded_shape();
|
apply_rounded_shape();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rounded corners: the webview paints an opaque rectangle, so round the whole top-level window
|
// Rounded corners: the webview paints an opaque rectangle, so round the whole top-level window.
|
||||||
// with a shape region (same mask trick as FilamentPickerDialog). Binary edges, no anti-aliasing.
|
// GTK/MSW use a shape region (same mask trick as FilamentPickerDialog, binary edges, no
|
||||||
|
// anti-aliasing); macOS clips the native view layer instead, since SetShape cannot shape there.
|
||||||
void SpeedDialWebDialog::apply_rounded_shape()
|
void SpeedDialWebDialog::apply_rounded_shape()
|
||||||
{
|
{
|
||||||
|
// wxOSX SetShape resizes the NSWindow (setContentSize 10x10 then back), which synchronously
|
||||||
|
// fires wxEVT_SIZE -> apply_rounded_shape() -> SetShape() and recurses until the stack
|
||||||
|
// overflows. GTK/MSW set a region without resizing, so they are unaffected.
|
||||||
|
if (m_applying_shape)
|
||||||
|
return;
|
||||||
|
|
||||||
// BORDER_NONE means the window is all client area, so the client size is the shape size.
|
// BORDER_NONE means the window is all client area, so the client size is the shape size.
|
||||||
const wxSize size = GetClientSize();
|
const wxSize size = GetClientSize();
|
||||||
if (size.GetWidth() <= 0 || size.GetHeight() <= 0)
|
if (size.GetWidth() <= 0 || size.GetHeight() <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_shape_bmp.Create(size.GetWidth(), size.GetHeight(), 32);
|
m_applying_shape = true;
|
||||||
if (!m_shape_bmp.IsOk())
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
#ifdef __WXOSX__
|
||||||
|
// wxOSX ignores the region (it only clears the window background), so round the native view.
|
||||||
|
set_window_corner_radius(this, FromDIP(m_corner_radius));
|
||||||
|
#else
|
||||||
|
m_shape_bmp.Create(size.GetWidth(), size.GetHeight(), 32);
|
||||||
|
if (m_shape_bmp.IsOk()) {
|
||||||
wxMemoryDC dc;
|
wxMemoryDC dc;
|
||||||
dc.SelectObject(m_shape_bmp);
|
dc.SelectObject(m_shape_bmp);
|
||||||
dc.SetBackground(wxBrush(wxColour(0, 0, 0)));
|
dc.SetBackground(wxBrush(wxColour(0, 0, 0)));
|
||||||
@@ -287,6 +311,10 @@ void SpeedDialWebDialog::apply_rounded_shape()
|
|||||||
wxRegion region(m_shape_bmp, wxColour(0, 0, 0));
|
wxRegion region(m_shape_bmp, wxColour(0, 0, 0));
|
||||||
if (region.IsOk())
|
if (region.IsOk())
|
||||||
SetShape(region);
|
SetShape(region);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_applying_shape = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpeedDialWebDialog::on_dpi_changed(const wxRect&)
|
void SpeedDialWebDialog::on_dpi_changed(const wxRect&)
|
||||||
@@ -379,7 +407,8 @@ void SpeedDialWebDialog::send_actions()
|
|||||||
{"actions", std::move(snap["actions"])},
|
{"actions", std::move(snap["actions"])},
|
||||||
{"favourites", std::move(snap["favourites"])},
|
{"favourites", std::move(snap["favourites"])},
|
||||||
{"recent", std::move(snap["recent"])},
|
{"recent", std::move(snap["recent"])},
|
||||||
{"user_mode", std::move(snap["user_mode"])}});
|
{"user_mode", std::move(snap["user_mode"])},
|
||||||
|
{"tooltip_expanded", std::move(snap["tooltip_expanded"])}});
|
||||||
}
|
}
|
||||||
|
|
||||||
}} // namespace Slic3r::GUI
|
}} // namespace Slic3r::GUI
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ private:
|
|||||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||||
|
|
||||||
bool m_page_ready{false};
|
bool m_page_ready{false};
|
||||||
// Rounded corners via a window shape region, since the webview itself is opaque.
|
// Rounded corners (shape region on GTK/MSW, native layer on macOS), since the webview is opaque.
|
||||||
int m_corner_radius{7};
|
int m_corner_radius{7};
|
||||||
wxBitmap m_shape_bmp;
|
wxBitmap m_shape_bmp;
|
||||||
|
// wxOSX SetShape resizes the window, which re-enters apply_rounded_shape() through wxEVT_SIZE.
|
||||||
|
bool m_applying_shape{false};
|
||||||
// Guards the CallAfter in on_script_message across dialog destruction, same as
|
// Guards the CallAfter in on_script_message across dialog destruction, same as
|
||||||
// PluginsDialog::m_alive (PluginsDialog.hpp:249).
|
// PluginsDialog::m_alive (PluginsDialog.hpp:249).
|
||||||
std::shared_ptr<std::atomic<bool>> m_alive = std::make_shared<std::atomic<bool>>(true);
|
std::shared_ptr<std::atomic<bool>> m_alive = std::make_shared<std::atomic<bool>>(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user