mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-15 15:03:49 +00:00
fix: resizable plugins dialog
This commit is contained in:
@@ -80,6 +80,11 @@
|
||||
<div id="pluginList" class="body thin-scroll"></div>
|
||||
</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">
|
||||
<div class="detail-tabs" role="tablist" aria-label="Plugin details">
|
||||
<button id="pluginInfoTab" class="detail-tab active" type="button" role="tab"
|
||||
|
||||
@@ -31,6 +31,16 @@ let ctxMenu = null;
|
||||
let exploreMenu = 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() {
|
||||
pluginList = document.getElementById("pluginList");
|
||||
ctxMenu = document.getElementById("ctxMenu");
|
||||
@@ -64,6 +74,8 @@ function OnInit() {
|
||||
pluginList?.addEventListener("contextmenu", OnPluginContextMenu);
|
||||
ctxMenu?.addEventListener("click", OnContextMenuClick);
|
||||
|
||||
InitPaneSplitter();
|
||||
|
||||
document.getElementById("configSidebar")?.addEventListener("click", OnConfigSidebarClick);
|
||||
document.getElementById("configSaveBtn")?.addEventListener("click", SaveCapabilityConfig);
|
||||
document.getElementById("configRestoreBtn")?.addEventListener("click", RestoreCapabilityConfig);
|
||||
@@ -97,6 +109,87 @@ function OnInit() {
|
||||
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) {
|
||||
const normalized = String(action || "");
|
||||
return pluginInstallActions[normalized] ? normalized : "explore";
|
||||
|
||||
@@ -151,11 +151,13 @@ body {
|
||||
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 {
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-rows: minmax(220px, 1fr) 280px;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pane {
|
||||
@@ -166,17 +168,60 @@ body {
|
||||
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 {
|
||||
flex: 0 1 auto;
|
||||
height: var(--plugin-list-height, 62%);
|
||||
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 {
|
||||
flex: 1 1 0;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
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 {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
const wxString& title = wxT(""),
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
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();
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ public:
|
||||
const wxString& title = wxT(""),
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
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;
|
||||
|
||||
bool create_webview(const std::string& resource_path,
|
||||
|
||||
Reference in New Issue
Block a user