mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-31 06:42:07 +00:00
fix: resizable plugins dialog
This commit is contained in:
@@ -80,6 +80,11 @@
|
|||||||
<div id="pluginList" class="body thin-scroll"></div>
|
<div id="pluginList" class="body thin-scroll"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Drag to redistribute the dialog's height between the list and the details pane; the hit
|
||||||
|
area is the whole strip, the visible divider is the line drawn inside it. -->
|
||||||
|
<div id="paneSplitter" class="pane-splitter" role="separator" aria-orientation="horizontal"
|
||||||
|
aria-label="Resize the plugin list" title="Drag to resize; double-click to reset"></div>
|
||||||
|
|
||||||
<section class="pane details-pane">
|
<section class="pane details-pane">
|
||||||
<div class="detail-tabs" role="tablist" aria-label="Plugin details">
|
<div class="detail-tabs" role="tablist" aria-label="Plugin details">
|
||||||
<button id="pluginInfoTab" class="detail-tab active" type="button" role="tab"
|
<button id="pluginInfoTab" class="detail-tab active" type="button" role="tab"
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ let ctxMenu = null;
|
|||||||
let exploreMenu = null;
|
let exploreMenu = null;
|
||||||
let exploreMenuButton = null;
|
let exploreMenuButton = null;
|
||||||
|
|
||||||
|
// Split pane. The ratio is the list's share of the content height, so it survives a dialog resize.
|
||||||
|
const SPLIT_STORAGE_KEY = "orca.plugins.split_ratio";
|
||||||
|
const SPLIT_DEFAULT_RATIO = 0.62;
|
||||||
|
const SPLIT_MIN_LIST_PX = 180;
|
||||||
|
const SPLIT_MIN_DETAILS_PX = 160;
|
||||||
|
|
||||||
|
let contentPane = null;
|
||||||
|
let paneSplitter = null;
|
||||||
|
let splitRatio = SPLIT_DEFAULT_RATIO;
|
||||||
|
|
||||||
function OnInit() {
|
function OnInit() {
|
||||||
pluginList = document.getElementById("pluginList");
|
pluginList = document.getElementById("pluginList");
|
||||||
ctxMenu = document.getElementById("ctxMenu");
|
ctxMenu = document.getElementById("ctxMenu");
|
||||||
@@ -64,6 +74,8 @@ function OnInit() {
|
|||||||
pluginList?.addEventListener("contextmenu", OnPluginContextMenu);
|
pluginList?.addEventListener("contextmenu", OnPluginContextMenu);
|
||||||
ctxMenu?.addEventListener("click", OnContextMenuClick);
|
ctxMenu?.addEventListener("click", OnContextMenuClick);
|
||||||
|
|
||||||
|
InitPaneSplitter();
|
||||||
|
|
||||||
document.getElementById("configSidebar")?.addEventListener("click", OnConfigSidebarClick);
|
document.getElementById("configSidebar")?.addEventListener("click", OnConfigSidebarClick);
|
||||||
document.getElementById("configSaveBtn")?.addEventListener("click", SaveCapabilityConfig);
|
document.getElementById("configSaveBtn")?.addEventListener("click", SaveCapabilityConfig);
|
||||||
document.getElementById("configRestoreBtn")?.addEventListener("click", RestoreCapabilityConfig);
|
document.getElementById("configRestoreBtn")?.addEventListener("click", RestoreCapabilityConfig);
|
||||||
@@ -97,6 +109,87 @@ function OnInit() {
|
|||||||
RequestPlugins();
|
RequestPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function InitPaneSplitter() {
|
||||||
|
contentPane = document.querySelector(".content");
|
||||||
|
paneSplitter = document.getElementById("paneSplitter");
|
||||||
|
if (!contentPane || !paneSplitter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ApplySplitRatio(ReadStoredSplitRatio());
|
||||||
|
|
||||||
|
paneSplitter.addEventListener("pointerdown", OnSplitterPointerDown);
|
||||||
|
paneSplitter.addEventListener("dblclick", () => {
|
||||||
|
ApplySplitRatio(SPLIT_DEFAULT_RATIO);
|
||||||
|
StoreSplitRatio(splitRatio);
|
||||||
|
});
|
||||||
|
// A resized dialog changes what the ratio is a ratio *of*, so re-clamp it against the new height
|
||||||
|
// rather than letting a pane fall below its minimum.
|
||||||
|
window.addEventListener("resize", () => ApplySplitRatio(splitRatio));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamped so neither pane drops below its minimum. When the dialog is too short to honor both, the
|
||||||
|
// panes just split what there is.
|
||||||
|
function ApplySplitRatio(ratio) {
|
||||||
|
const available = contentPane.clientHeight;
|
||||||
|
const sash = paneSplitter.offsetHeight;
|
||||||
|
let next = Number.isFinite(ratio) ? ratio : SPLIT_DEFAULT_RATIO;
|
||||||
|
|
||||||
|
if (available > 0) {
|
||||||
|
const min = SPLIT_MIN_LIST_PX / available;
|
||||||
|
const max = (available - sash - SPLIT_MIN_DETAILS_PX) / available;
|
||||||
|
next = min > max ? 0.5 : Math.min(Math.max(next, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
splitRatio = next;
|
||||||
|
contentPane.style.setProperty("--plugin-list-height", `${(next * 100).toFixed(2)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnSplitterPointerDown(event) {
|
||||||
|
if (event.button !== 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bounds = contentPane.getBoundingClientRect();
|
||||||
|
// Offset of the grab point inside the strip, so the splitter does not jump under the cursor.
|
||||||
|
const grabOffset = event.clientY - paneSplitter.getBoundingClientRect().top;
|
||||||
|
|
||||||
|
const onMove = (moveEvent) => {
|
||||||
|
ApplySplitRatio((moveEvent.clientY - bounds.top - grabOffset) / bounds.height);
|
||||||
|
};
|
||||||
|
const onUp = (upEvent) => {
|
||||||
|
paneSplitter.releasePointerCapture(upEvent.pointerId);
|
||||||
|
paneSplitter.removeEventListener("pointermove", onMove);
|
||||||
|
paneSplitter.removeEventListener("pointerup", onUp);
|
||||||
|
paneSplitter.classList.remove("dragging");
|
||||||
|
document.body.classList.remove("pane-resizing");
|
||||||
|
StoreSplitRatio(splitRatio);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Captured, so the drag keeps tracking once the pointer leaves the strip (which it does at once).
|
||||||
|
paneSplitter.setPointerCapture(event.pointerId);
|
||||||
|
paneSplitter.addEventListener("pointermove", onMove);
|
||||||
|
paneSplitter.addEventListener("pointerup", onUp);
|
||||||
|
paneSplitter.classList.add("dragging");
|
||||||
|
document.body.classList.add("pane-resizing");
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReadStoredSplitRatio() {
|
||||||
|
try {
|
||||||
|
const stored = Number.parseFloat(window.localStorage.getItem(SPLIT_STORAGE_KEY));
|
||||||
|
return Number.isFinite(stored) ? stored : SPLIT_DEFAULT_RATIO;
|
||||||
|
} catch (err) {
|
||||||
|
return SPLIT_DEFAULT_RATIO; // storage can be unavailable in the webview; the default still works
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function StoreSplitRatio(ratio) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(SPLIT_STORAGE_KEY, String(ratio));
|
||||||
|
} catch (err) {
|
||||||
|
// Persisting the position is a nicety, never a reason to break the drag.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function NormalizeInstallAction(action) {
|
function NormalizeInstallAction(action) {
|
||||||
const normalized = String(action || "");
|
const normalized = String(action || "");
|
||||||
return pluginInstallActions[normalized] ? normalized : "explore";
|
return pluginInstallActions[normalized] ? normalized : "explore";
|
||||||
|
|||||||
@@ -151,11 +151,13 @@ body {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Split pane: the list keeps --plugin-list-height of the dialog's content height (set by the
|
||||||
|
splitter drag, see InitPaneSplitter), the details pane takes what is left. Both panes shrink
|
||||||
|
before the layout overflows, so a short dialog degrades instead of clipping. */
|
||||||
.content {
|
.content {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-rows: minmax(220px, 1fr) 280px;
|
flex-direction: column;
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane {
|
.pane {
|
||||||
@@ -166,17 +168,60 @@ body {
|
|||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The pane minimums live in JS (SPLIT_MIN_LIST_PX / SPLIT_MIN_DETAILS_PX), which clamps the ratio
|
||||||
|
against the current height: as CSS min-heights they could not both be honored in a very short
|
||||||
|
dialog and the panes would overflow instead of shrinking. */
|
||||||
.plugin-list-pane {
|
.plugin-list-pane {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
height: var(--plugin-list-height, 62%);
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* basis 0, so the details pane takes the space the list leaves instead of letting its own content
|
||||||
|
height push back and shrink the list below --plugin-list-height. */
|
||||||
.details-pane {
|
.details-pane {
|
||||||
|
flex: 1 1 0;
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 9px of grab area for a 2px divider: the strip also supplies the gap between the panes. */
|
||||||
|
.pane-splitter {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
position: relative;
|
||||||
|
height: 9px;
|
||||||
|
cursor: ns-resize;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-splitter::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 2px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border-radius: 1px;
|
||||||
|
background: transparent;
|
||||||
|
transition: background-color 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-splitter:hover::after {
|
||||||
|
background: var(--border-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-splitter.dragging::after {
|
||||||
|
background: var(--row-selected-outline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep the resize cursor for the whole drag, wherever the pointer travels. */
|
||||||
|
body.pane-resizing {
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
.hdr {
|
.hdr {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public:
|
|||||||
const wxString& title = wxT(""),
|
const wxString& title = wxT(""),
|
||||||
const wxPoint& pos = wxDefaultPosition,
|
const wxPoint& pos = wxDefaultPosition,
|
||||||
const wxSize& size = wxDefaultSize,
|
const wxSize& size = wxDefaultSize,
|
||||||
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX);
|
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX | wxRESIZE_BORDER);
|
||||||
|
|
||||||
~PluginsDialog();
|
~PluginsDialog();
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ public:
|
|||||||
const wxString& title = wxT(""),
|
const wxString& title = wxT(""),
|
||||||
const wxPoint& pos = wxDefaultPosition,
|
const wxPoint& pos = wxDefaultPosition,
|
||||||
const wxSize& size = wxDefaultSize,
|
const wxSize& size = wxDefaultSize,
|
||||||
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX);
|
// wxRESIZE_BORDER is required for a resizable frame on MSW/GTK; macOS derives
|
||||||
|
// one from wxMAXIMIZE_BOX alone, which is why these dialogs used to resize only there.
|
||||||
|
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX | wxRESIZE_BORDER);
|
||||||
~WebViewHostDialog() override = default;
|
~WebViewHostDialog() override = default;
|
||||||
|
|
||||||
bool create_webview(const std::string& resource_path,
|
bool create_webview(const std::string& resource_path,
|
||||||
|
|||||||
Reference in New Issue
Block a user