Add Page Editor toolbar toggles and widen the default window
Edit mode, snap to grid and the widgets/properties pane are now toggles on the main toolbar while the Page Editor is open. Outside edit mode the page and the property sheet are read-only; drags snap to the 10px grid when snapping is on. The default window grows by the right pane's width so the canvas is not squeezed when the pane shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+80
-30
@@ -53,6 +53,8 @@ DEFAULT_ZOOM = 0.5
|
||||
ZOOM_LEVELS = (0.25, 0.33, 0.5, 0.67, 0.8, 1.0, 1.25, 1.5, 2.0, 3.0)
|
||||
|
||||
CANVAS_PADDING = 16
|
||||
# About what the widget tree / properties pane asks for (300px sheet + scrollbar).
|
||||
RIGHT_PANE_WIDTH = 320
|
||||
|
||||
WIDGET_OUTLINE_WIDTH = 1
|
||||
SELECTED_OUTLINE = "#1f4e79"
|
||||
@@ -146,8 +148,12 @@ GRID_STEP = 10 # device pixels
|
||||
GRID_MIN_SPACING = 4 # canvas pixels; a denser grid is left out rather than drawn solid
|
||||
GRID_SHADE = 0.82 # grid line = page colour scaled by this (lifted on dark pages)
|
||||
|
||||
# Whether the 10x10 grid is drawn, held for the session like the Sedona sheet's toggles.
|
||||
show_grid = True
|
||||
# Page Editor toggles, held for the session like the Sedona sheet's: whether the page
|
||||
# can be changed (edit mode, which is also when the 10x10 grid is drawn), whether a
|
||||
# drag lands on the grid, and whether the tree/properties pane shows.
|
||||
edit_mode = False
|
||||
snap_to_grid = True
|
||||
show_right_pane = True
|
||||
|
||||
|
||||
def _grid_colour(page_hex):
|
||||
@@ -200,6 +206,7 @@ class OxPageViewPanel:
|
||||
self._build_header()
|
||||
self._build_body()
|
||||
self._build_footer()
|
||||
self._apply_right_pane()
|
||||
self.refresh()
|
||||
|
||||
@property
|
||||
@@ -233,10 +240,9 @@ class OxPageViewPanel:
|
||||
tk.Label(header, textvariable=self._widget_count_var, font=plain_font,
|
||||
fg="#555555", bg=HEADER_BG).pack(side=tk.RIGHT)
|
||||
|
||||
self._grid_var = tk.BooleanVar(value=show_grid)
|
||||
tk.Checkbutton(header, text="Grid", variable=self._grid_var, font=plain_font,
|
||||
bg=HEADER_BG, activebackground=HEADER_BG,
|
||||
command=self._on_grid_toggled).pack(side=tk.RIGHT, padx=(0, 12))
|
||||
self._edit_var = tk.BooleanVar(value=edit_mode)
|
||||
self._snap_var = tk.BooleanVar(value=snap_to_grid)
|
||||
self._right_pane_var = tk.BooleanVar(value=show_right_pane)
|
||||
|
||||
def _build_body(self):
|
||||
body = ttk.PanedWindow(self.frame, orient=tk.HORIZONTAL)
|
||||
@@ -249,12 +255,15 @@ class OxPageViewPanel:
|
||||
# Workbench's layout: the widget tree above, the selected widget's sheet below.
|
||||
side = ttk.PanedWindow(body, orient=tk.VERTICAL)
|
||||
body.add(side, weight=0)
|
||||
self._body = body
|
||||
self._side = side
|
||||
|
||||
tree_frame = ttk.Frame(side)
|
||||
self._build_widget_tree(tree_frame)
|
||||
side.add(tree_frame, weight=1)
|
||||
|
||||
self._props = OxWidgetPropertiesPanel(side, self._commit_props, self._repaint_all)
|
||||
self._props.set_editable(self._edit_var.get())
|
||||
side.add(self._props.frame, weight=3)
|
||||
|
||||
def _build_widget_tree(self, parent):
|
||||
@@ -319,7 +328,7 @@ class OxPageViewPanel:
|
||||
self._painter = WidgetPainter(c, sx, sy, z, bg_colour)
|
||||
self._painter.page_image(_get_prop(self.comp, "imageFileName", ""),
|
||||
self._screen_w, self._screen_h)
|
||||
if self._grid_var.get():
|
||||
if self._edit_var.get():
|
||||
self._draw_grid(sx, sy, _grid_colour(bg_colour))
|
||||
|
||||
if self._frame_image:
|
||||
@@ -364,10 +373,48 @@ class OxPageViewPanel:
|
||||
for gy in range(GRID_STEP, self._screen_h, GRID_STEP):
|
||||
c.create_line(sx, sy + gy * z, right, sy + gy * z, fill=colour, tags=("grid",))
|
||||
|
||||
def _on_grid_toggled(self):
|
||||
global show_grid
|
||||
show_grid = self._grid_var.get()
|
||||
def _on_edit_toggled(self):
|
||||
global edit_mode
|
||||
edit_mode = self._edit_var.get()
|
||||
if not edit_mode:
|
||||
self._props.flush_edits()
|
||||
self._props.set_editable(edit_mode)
|
||||
self.refresh()
|
||||
app_state.set_status("Page Editor: edit mode — the page can be changed."
|
||||
if edit_mode else "Page Editor: view mode — nothing can be changed.")
|
||||
|
||||
def _on_snap_toggled(self):
|
||||
global snap_to_grid
|
||||
snap_to_grid = self._snap_var.get()
|
||||
|
||||
def _on_right_pane_toggled(self):
|
||||
global show_right_pane
|
||||
show_right_pane = self._right_pane_var.get()
|
||||
self._apply_right_pane()
|
||||
|
||||
def _apply_right_pane(self):
|
||||
"""Takes the widget tree and properties sheet out of the split, or puts them back."""
|
||||
showing = str(self._side) in self._body.panes()
|
||||
if self._right_pane_var.get() and not showing:
|
||||
self._body.add(self._side, weight=0)
|
||||
elif not self._right_pane_var.get() and showing:
|
||||
self._props.flush_edits()
|
||||
self._body.forget(self._side)
|
||||
|
||||
# ── main toolbar ──────────────────────────────────────────────
|
||||
|
||||
def toolbar_tools(self):
|
||||
"""The Page Editor's own toolbar buttons: (icon, tooltip, variable, command).
|
||||
|
||||
`main` lays them out on its toolbar while this editor is open and takes them
|
||||
down with it. A button with a variable is a toggle; add new tools here.
|
||||
"""
|
||||
return [
|
||||
("✏", "Edit Mode", self._edit_var, self._on_edit_toggled),
|
||||
("⌗", "Snap to Grid", self._snap_var, self._on_snap_toggled),
|
||||
("◨", "Widgets / Properties Pane", self._right_pane_var,
|
||||
self._on_right_pane_toggled),
|
||||
]
|
||||
|
||||
def _draw_widget(self, widget):
|
||||
"""Paints the widget as the device would, then lays an empty box over it.
|
||||
@@ -526,9 +573,12 @@ class OxPageViewPanel:
|
||||
widget = self._widget_at(cx, cy)
|
||||
if widget is not None:
|
||||
self._select_widget(widget)
|
||||
self._drag["tag"] = self._widget_items[widget]
|
||||
self._drag["x"] = cx
|
||||
self._drag["y"] = cy
|
||||
if not self._edit_var.get():
|
||||
self._drag["tag"] = None
|
||||
return
|
||||
box = self._widget_boxes[widget]
|
||||
self._drag.update(tag=self._widget_items[widget], x=cx, y=cy,
|
||||
origin=(box[0], box[1]), pos=(box[0], box[1]))
|
||||
else:
|
||||
self._select_widget(None)
|
||||
self._drag["tag"] = None
|
||||
@@ -538,19 +588,27 @@ class OxPageViewPanel:
|
||||
return
|
||||
|
||||
c = self._canvas
|
||||
z = self._zoom
|
||||
cx = c.canvasx(event.x)
|
||||
cy = c.canvasy(event.y)
|
||||
|
||||
dx = cx - self._drag["x"]
|
||||
dy = cy - self._drag["y"]
|
||||
# Worked out from where the drag began rather than step by step, so snapping
|
||||
# never accumulates rounding and the widget lands exactly where it is drawn.
|
||||
ox, oy = self._drag["origin"]
|
||||
new_x = ox + (cx - self._drag["x"]) / z
|
||||
new_y = oy + (cy - self._drag["y"]) / z
|
||||
if self._snap_var.get():
|
||||
new_x = round(new_x / GRID_STEP) * GRID_STEP
|
||||
new_y = round(new_y / GRID_STEP) * GRID_STEP
|
||||
new_x = int(round(max(0, min(new_x, self._screen_w))))
|
||||
new_y = int(round(max(0, min(new_y, self._screen_h))))
|
||||
|
||||
tag = self._drag["tag"]
|
||||
c.move(tag, dx, dy)
|
||||
px, py = self._drag["pos"]
|
||||
if (new_x, new_y) != (px, py):
|
||||
c.move(self._drag["tag"], (new_x - px) * z, (new_y - py) * z)
|
||||
self._drag["pos"] = (new_x, new_y)
|
||||
c.delete("selection_handle")
|
||||
|
||||
self._drag["x"] = cx
|
||||
self._drag["y"] = cy
|
||||
|
||||
def _on_canvas_release(self, event):
|
||||
tag = self._drag["tag"]
|
||||
if not tag:
|
||||
@@ -562,17 +620,9 @@ class OxPageViewPanel:
|
||||
return
|
||||
|
||||
self._drag["tag"] = None
|
||||
c = self._canvas
|
||||
z = self._zoom
|
||||
bbox = c.bbox(f"{tag} && ox_widget_rect")
|
||||
moved = False
|
||||
if bbox:
|
||||
new_x = int((bbox[0] - self._screen_canvas_x * z) / z)
|
||||
new_y = int((bbox[1] - self._screen_canvas_y * z) / z)
|
||||
|
||||
new_x = max(0, min(new_x, self._screen_w))
|
||||
new_y = max(0, min(new_y, self._screen_h))
|
||||
|
||||
new_x, new_y = self._drag["pos"]
|
||||
if (new_x, new_y) != self._drag["origin"]:
|
||||
moved = self._commit_props(widget, {"x": str(new_x), "y": str(new_y)})
|
||||
|
||||
# A commit re-renders through refresh(); a drag that went nowhere still has to
|
||||
|
||||
Reference in New Issue
Block a user