Skip to content

Commit

Permalink
Implement widget stacking control
Browse files Browse the repository at this point in the history
  • Loading branch information
ObaraEmmanuel committed Sep 8, 2023
1 parent e9ed6ae commit 9ea3d55
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 39 deletions.
7 changes: 7 additions & 0 deletions studio/feature/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ def on_widgets_layout_change(self, widgets):
"""
pass

def on_widgets_reorder(self, indices):
"""
Called when the widgets in the designer are reordered within their parent.
Used to change stacking order of widgets
"""
pass

def on_widget_add(self, widget, parent):
"""
Called when a new widget is added to the designer
Expand Down
66 changes: 63 additions & 3 deletions studio/feature/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def add_widget(self, widget, bounds=None, **kwargs):
else:
x1, y1, x2, y2 = self.container.canvas_bounds(bounds)
self.container.place_child(widget, x=x1, y=y1, width=x2 - x1, height=y2 - y1)
self.children.append(widget)
self._insert(widget, widget.prev_stack_index if widget.layout == self.container else None)

def remove_widget(self, widget):
super().remove_widget(widget)
Expand All @@ -68,7 +68,7 @@ def apply(self, prop, value, widget):

def restore_widget(self, widget, data=None):
data = widget.recent_layout_info if data is None else data
self.children.append(widget)
self._insert(widget, widget.prev_stack_index if widget.layout == self.container else None)
widget.layout = self.container
widget.level = self.level + 1
self.container.place_child(widget, **data.get("info", {}))
Expand Down Expand Up @@ -333,6 +333,7 @@ def _load_design(self, path, progress=None):
accelerator = actions.get_routine("STUDIO_RELOAD").accelerator
text = f"{str(e)}\nPress {accelerator} to reload" if accelerator else f"{str(e)} \n reload design"
self._show_empty(True, text=text, image=get_tk_image("dialog_error", 50, 50))
raise e
# MessageDialog.show_error(parent=self.studio, title='Error loading design', message=str(e))
finally:
if progress:
Expand Down Expand Up @@ -762,10 +763,10 @@ def _text_change(self):
def _show_text_editor(self, widget):
if any("text" not in w.keys() for w in self.selected):
return
self._text_editor.lift(widget)
cnf = self._collect_text_config(widget)
self._text_editor.config(**cnf)
self._text_editor.place(in_=widget, relwidth=1, relheight=1, x=0, y=0)
self._text_editor.lift(widget)
self._text_editor.clear()
self._text_editor.focus_set()
# suppress change event while we set initial value
Expand All @@ -792,6 +793,65 @@ def _collect_text_config(self, widget):
def _text_hide(self, *_):
self._text_editor.place_forget()

def send_back(self, steps=0):
if not (self.studio.selection and self.studio.selection.is_same_parent()):
return

child_list = next(iter(self.studio.selection)).layout._children
widgets = sorted(self.studio.selection, key=child_list.index)

if steps == 0:
self._update_stacking({w: index for index, w in enumerate(widgets)})
else:
self._update_stacking({w: max(0, child_list.index(w) - steps) for w in widgets})

def bring_front(self, steps=0):
if not (self.studio.selection and self.studio.selection.is_same_parent()):
return

child_list = next(iter(self.studio.selection)).layout._children
widgets = sorted(self.studio.selection, key=child_list.index)

end = len(child_list) - 1
if steps == 0:
self._update_stacking({w: end for w in widgets})
else:
self._update_stacking({w: min(end, child_list.index(w) + steps) for w in widgets})

def _update_stacking(self, indices, silently=False):
if not indices:
return

child_list = next(iter(indices)).layout._children
# reorder child list based on indices
for widget in indices:
child_list.remove(widget)
for widget, index in indices.items():
child_list.insert(index, widget)

prev_data = {}
data = {}
for index, widget in enumerate(child_list):
if widget.prev_stack_index != index:
prev_data[widget] = widget.prev_stack_index
data[widget] = index
widget.prev_stack_index = index
if index > 0:
widget.lift(child_list[index - 1])
else:
widget.lift(widget.layout.body)

prev_data = dict(sorted(prev_data.items(), key=lambda x: x[1]))

if not silently and prev_data != data:
self.studio.new_action(Action(
lambda _: self._update_stacking(prev_data, True),
lambda _: self._update_stacking(data, True)
))

def on_widgets_reorder(self, indices):
pass

def on_widgets_change(self, widgets):
pass

Expand Down
7 changes: 7 additions & 0 deletions studio/lib/handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ def on_dot_move(self, direction, delta):
def widget_config_changed(self):
pass

def lift(self):
for dot in self.dots:
dot.lift()
for edge in self.edges:
edge.lift()

def redraw(self):
raise NotImplementedError

Expand Down Expand Up @@ -154,6 +160,7 @@ def acquire(cls, widget, master=None):
else:
obj = cls._pool[(cls, master)].pop()
obj.widget = widget
obj.lift()
return obj


Expand Down
80 changes: 47 additions & 33 deletions studio/lib/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class BaseLayoutStrategy:
realtime_support = False # dictates whether strategy supports realtime updates to its values, most do not
dimensions_in_px = False # Whether to use pixel units for width and height
allow_resize = False # Whether to allow resizing of widgets
stacking_support = True # Whether to allow modification of stacking order

def __init__(self, container):
self.parent = container.parent
Expand All @@ -80,12 +81,25 @@ def bounds(self):
def add_widget(self, widget, bounds=None, **kwargs):
widget.level = self.level + 1
widget.layout = self.container
try:
widget.lift(self.container.body)
except Exception:
pass
self.container.clear_highlight()

def _insert(self, widget, index=None):
if index is None:
self.children.append(widget)
else:
self.children.insert(index, widget)
self._update_stacking()

if widget.prev_stack_index is None:
widget.prev_stack_index = len(self.children) - 1

def _update_stacking(self):
for index, widget in enumerate(self.children):
if index > 0:
widget.lift(self.children[index - 1])
else:
widget.lift(self.container.body)

def widget_released(self, widget):
pass

Expand All @@ -99,13 +113,14 @@ def move_widget(self, widget, bounds):
self.remove_widget(widget)
if widget not in self.temporal_children:
self.temporal_children.append(widget)
if widget.layout != self.container:
# if widget was originally in a different layout
# Lift widget above the last child of layout if any otherwise lift above the layout
widget.lift((self.children[-1:] or [self.container.body])[0])

widget.level = self.level + 1
widget.layout = self.container
# Lift widget above the last child of layout if any otherwise lift above the layout
try:
widget.lift((self.children[-1:] or [self.container.body])[0])
except Exception:
pass

self._move(widget, bounds)

def end_move(self):
Expand Down Expand Up @@ -250,7 +265,7 @@ def add_widget(self, widget, bounds=None, **kwargs):
self.move_widget(widget, bounds)
kwargs['in'] = self.container.body
widget.place_configure(**kwargs)
self.children.append(widget)
self._insert(widget, widget.prev_stack_index if widget.layout == self.container else None)

def _info_with_delta(self, widget, direction, delta):
info = self.info(widget)
Expand Down Expand Up @@ -287,14 +302,11 @@ def config_widget(self, widget, config):

def restore_widget(self, widget, data=None):
data = widget.recent_layout_info if data is None else data
self.children.append(widget)
self._insert(widget, widget.prev_stack_index if widget.layout == self.container else None)

widget.layout = self.container
widget.level = self.level + 1
widget.place_configure(**data.get("info", {}))
try:
widget.lift((self.children[-1:] or [self.container.body])[0])
except Exception:
pass

def get_restore(self, widget):
return {
Expand All @@ -313,7 +325,7 @@ def copy_layout(self, widget, from_):
info = from_.place_info()
info["in_"] = self.container.body
widget.place(**info)
self.children.append(widget)
self._insert(widget)
super().add_widget(widget, (0, 0, 0, 0))


Expand Down Expand Up @@ -352,6 +364,7 @@ class PackLayoutStrategy(BaseLayoutStrategy):
name = "pack"
icon = "frame"
manager = "pack"
stacking_support = False

def __init__(self, container):
super().__init__(container)
Expand All @@ -368,7 +381,7 @@ def add_widget(self, widget, bounds=None, **kwargs):
elif self._orientation == self.VERTICAL:
widget.pack(in_=self.container.body, side="left")
self.config_widget(widget, kwargs)
self.children.append(widget)
self._insert(widget)

def redraw(self):
for widget in self.children:
Expand Down Expand Up @@ -464,7 +477,7 @@ def copy_layout(self, widget, from_):
info = from_.pack_info()
info["in_"] = self.container.body
widget.pack(**info)
self.children.append(widget)
self._insert(widget)
super().add_widget(widget, (0, 0, 0, 0))

def clear_all(self):
Expand Down Expand Up @@ -509,7 +522,7 @@ def add_widget(self, widget, bounds=None, **kwargs):
super().add_widget(widget, bounds, **kwargs)
width, height = geometry.dimensions(bounds)
self.attach(widget, width, height)
self.children.append(widget)
self._insert(widget)

def attach(self, widget, width, height):
y = self.get_last()
Expand All @@ -525,7 +538,7 @@ def redraw(self, widget):
child.place_forget()
for child in temp:
self.attach(child, *dimensions[child])
self._children.append(child)
self._insert(child)

def resize_widget(self, widget, direction, delta):
widget.update_idletasks()
Expand All @@ -541,7 +554,7 @@ def remove_widget(self, widget):
self._children = self._children[:from_]
for child in temp:
self.attach(child, *dimensions[child])
self._children.append(child)
self._insert(child)

def clear_children(self):
for child in self.children:
Expand Down Expand Up @@ -653,11 +666,10 @@ def get_restore(self, widget):

def restore_widget(self, widget, data=None):
data = widget.recent_layout_info if data is None else data
self.children.append(widget)
self._insert(widget, widget.prev_stack_index if widget.layout == self.container else None)
widget.level = self.level + 1
widget.layout = self.container
widget.grid(in_=self.container.body)
widget.lift()
self.config_widget(widget, data.get("info", {}))

def react_to(self, bounds):
Expand Down Expand Up @@ -746,7 +758,7 @@ def add_widget(self, widget, bounds=None, **kwargs):
else:
widget.grid(in_=self.container.body)
self.config_widget(widget, kwargs)
self.children.append(widget)
self._insert(widget, widget.prev_stack_index if widget.layout == self.container else None)
self.clear_indicators()

def _widget_at(self, row, column):
Expand Down Expand Up @@ -836,7 +848,7 @@ def copy_layout(self, widget, from_):
info = from_.grid_info()
info["in_"] = self.container.body
widget.grid(**info)
self.children.append(widget)
self._insert(widget)
super().add_widget(widget, (0, 0, 0, 0))

def clear_all(self):
Expand All @@ -847,9 +859,6 @@ def clear_all(self):

class TabLayoutStrategy(BaseLayoutStrategy):
# TODO Extend support for side specific padding
name = "TabLayout"
icon = "notebook"
manager = "tab"
DEFINITION = {
"text": {
"display_name": "tab text",
Expand Down Expand Up @@ -897,6 +906,10 @@ class TabLayoutStrategy(BaseLayoutStrategy):
"default": 'normal'
}
}
name = "TabLayout"
icon = "notebook"
manager = "tab"
stacking_support = False

def __init__(self, master):
super().__init__(master)
Expand Down Expand Up @@ -940,7 +953,7 @@ def add_widget(self, widget, bounds=None, **kwargs):
super().add_widget(widget, bounds, **kwargs)
self.container.body.add(widget, text=widget.id)
self.container.body.tab(widget, **kwargs)
self.children.append(widget)
self._insert(widget)

def remove_widget(self, widget):
super().remove_widget(widget)
Expand Down Expand Up @@ -975,9 +988,6 @@ def clear_all(self):


class PanedLayoutStrategy(BaseLayoutStrategy):
name = "PanedLayout"
icon = "flip_horizontal"
manager = "pane"
DEFINITION = {
**BaseLayoutStrategy.DEFINITION, # width and height definition
"padx": COMMON_DEFINITION.get("padx"),
Expand Down Expand Up @@ -1009,6 +1019,10 @@ class PanedLayoutStrategy(BaseLayoutStrategy):
"name": "minsize",
}
}
name = "PanedLayout"
icon = "flip_horizontal"
manager = "pane"
stacking_support = False

def get_restore(self, widget):
return {
Expand Down Expand Up @@ -1050,7 +1064,7 @@ def add_widget(self, widget, bounds=None, **kwargs):
super().add_widget(widget, bounds, **kwargs)
self.container.body.add(widget)
self._config(widget, **kwargs)
self.children.append(widget)
self._insert(widget)

def _config(self, widget, **kwargs):
if not kwargs:
Expand Down
Loading

0 comments on commit 9ea3d55

Please sign in to comment.