Skip to content

Commit

Permalink
Implement scroll options in studio
Browse files Browse the repository at this point in the history
  • Loading branch information
ObaraEmmanuel committed Feb 26, 2024
1 parent 0bed3eb commit a5044a6
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 1 deletion.
1 change: 1 addition & 0 deletions formation/formats/_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"layout": "http://www.hoversetformationstudio.com/layouts/",
"attr": "http://www.hoversetformationstudio.com/styles/",
"menu": "http://www.hoversetformationstudio.com/menu",
"scroll": "http://www.hoversetformationstudio.com/scroll",
}
_reversed_namespaces = dict(zip(namespaces.values(), namespaces.keys()))
_attr_rgx = re.compile(r"{(?P<namespace>.+)}(?P<attr>.+)")
Expand Down
80 changes: 79 additions & 1 deletion studio/feature/stylepane.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import logging
from collections import defaultdict
import tkinter as tk
from tkinter import ttk

from hoverset.ui.icons import get_icon_image
from hoverset.ui.widgets import ScrolledFrame, Frame, Label, Button, TabView
Expand Down Expand Up @@ -508,7 +510,6 @@ def __init__(self, master, pane, **cnf):
super().__init__(master, pane, **cnf)
self.label = "Window"
self._prev_layout = None
self._grid_config = GridConfig(self.body, pane)

def _get_prop(self, prop, widget):
return widget.get_win_prop(prop)
Expand All @@ -528,6 +529,82 @@ def supports_widgets(self):
return all(widget.is_toplevel for widget in self.widgets)


class ScrollGroup(StyleGroup):
handles_layout = False

DEF = {
"yscroll": {
"name": "yscroll",
"display_name": "Y Scroll",
"type": "widget",
"include": [tk.Scrollbar, ttk.Scrollbar],
},
"xscroll": {
"name": "xscroll",
"display_name": "X Scroll",
"type": "widget",
"include": [tk.Scrollbar, ttk.Scrollbar],
},
}

def __init__(self, master, pane, **cnf):
super().__init__(master, pane, **cnf)
self.label = "Scroll"
self._last_keys = None

def _get_prop(self, prop, widget):
if prop == "yscroll":
return getattr(widget, "_cnf_y_scroll", '')
if prop == "xscroll":
return getattr(widget, "_cnf_x_scroll", '')

def _set_prop(self, prop, value, widget):
if prop == "yscroll":
widget._cnf_y_scroll = value
if prop == "xscroll":
widget._cnf_x_scroll = value

def _keys(self, widget):
keys = []
widget_keys = widget.keys()
if 'yscrollcommand' in widget_keys:
keys.append('yscroll')
if 'xscrollcommand' in widget_keys:
keys.append('xscroll')
return tuple(keys)

def can_optimize(self):
keys = set()
for widget in self.widgets:
for k in self._keys(widget):
keys.add(k)
if keys != self._last_keys:
self._last_keys = keys
return False
return True

def _definition(self, widget):
keys = widget.keys()
props = {}
if 'yscrollcommand' in keys:
props['yscroll'] = dict(**self.DEF['yscroll'], value=getattr(widget, '_cnf_y_scroll', ''))
if 'xscrollcommand' in keys:
props['xscroll'] = dict(**self.DEF['xscroll'], value=getattr(widget, '_cnf_x_scroll', ''))
return props

def get_definition(self):
if self.widgets:
return combine_properties([self._definition(widget) for widget in self.widgets])
return {}

def _support(self, widget):
keys = widget.keys()
return any(x in keys for x in ('yscrollcommand', 'xscrollcommand'))

def supports_widgets(self):
return all(self._support(w) for w in self.widgets)


class StylePaneFramework:

def setup_style_pane(self):
Expand Down Expand Up @@ -752,6 +829,7 @@ def __init__(self, master, studio, **cnf):
self._layout_group = self.add_group(LayoutGroup)
self._attribute_group = self.add_group(AttributeGroup)
self.add_group(WindowGroup)
self.add_group(ScrollGroup)

self.add_group_instance(self._layout_group._grid_config.column_config)
self.add_group_instance(self._layout_group._grid_config.row_config)
Expand Down
1 change: 1 addition & 0 deletions studio/lib/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ class Scrollbar(PseudoWidget, tk.Scrollbar):
icon = "scrollbar"
impl = tk.Scrollbar
initial_dimensions = 20, 100
allow_direct_move = False

def __init__(self, master, id_):
super().__init__(master)
Expand Down
1 change: 1 addition & 0 deletions studio/lib/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ class Scrollbar(PseudoWidget, ttk.Scrollbar):
icon = "scrollbar"
impl = ttk.Scrollbar
initial_dimensions = 20, 100
allow_direct_move = False

def __init__(self, master, id_):
super().__init__(master)
Expand Down
4 changes: 4 additions & 0 deletions studio/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ def designer(self):
if isinstance(self.context, DesignContext):
return self.context.designer

def get_widgets(self):
if self.designer:
return self.designer.objects

def _show_empty(self, text):
if text:
self._tab_view_empty.lift()
Expand Down
28 changes: 28 additions & 0 deletions studio/parsers/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ def generate(cls, widget: PseudoWidget, parent=None):
layout_options = widget.layout.get_altered_options_for(widget)
node["layout"] = layout_options

scroll_conf = {}
if isinstance(getattr(widget, "_cnf_x_scroll", None), PseudoWidget):
scroll_conf["x"] = widget._cnf_x_scroll.id
if isinstance(getattr(widget, "_cnf_y_scroll", None), PseudoWidget):
scroll_conf["y"] = widget._cnf_y_scroll.id
if scroll_conf:
node["scroll"] = scroll_conf

if hasattr(widget, "_event_map_"):
for binding in widget._event_map_.values():
bind_dict = binding._asdict()
Expand Down Expand Up @@ -119,6 +127,14 @@ def load(cls, node, designer, parent, bounds=None):
styles.pop('orient')
layout = attrib.get("layout", {})
obj = designer.load(obj_class, attrib["name"], parent, styles, layout, bounds)

# load scroll configuration
scroll_conf = attrib.get("scroll", {})
if scroll_conf.get("x"):
obj._cnf_x_scroll = scroll_conf["x"]
if scroll_conf.get("y"):
obj._cnf_y_scroll = scroll_conf["y"]

for sub_node in node:
if sub_node.type == "event":
binding = make_event(**sub_node.attrib)
Expand Down Expand Up @@ -333,8 +349,20 @@ def _load_widgets(self, node, designer, parent, bounds=None):
continue
self._load_widgets(sub_node, designer, widget)
Meth.call_deferred(designer)
self._post_process(designer)
return widget

def _post_process(self, designer):
_lookup = {}
for obj in designer.objects:
_lookup[obj.id] = obj

for w in designer.objects:
if hasattr(w, "_cnf_y_scroll"):
w._cnf_y_scroll = _lookup.get(w._cnf_y_scroll, '')
if hasattr(w, "_cnf_x_scroll"):
w._cnf_x_scroll = _lookup.get(w._cnf_x_scroll, '')

def to_tree(self, widget, parent=None, with_node=None):
"""
Convert a PseudoWidget widget and its children to a node
Expand Down
50 changes: 50 additions & 0 deletions studio/ui/editors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pathlib
import re
import sys
import tkinter as tk
from tkinter import BooleanVar, filedialog, StringVar

from hoverset.ui.icons import get_icon_image
Expand Down Expand Up @@ -683,6 +684,55 @@ def on_var_context_change(self):
))


class Widget(Choice):
_setup_once = False

class WidgetChoiceItem(Choice.ChoiceItem):

def render(self):
if self.value:
item = Label(
self, text=f" {self.value.id}", **self.style.text,
image=get_icon_image(self.value.icon, 15, 15),
compound="left", anchor="w"
)
item.pack(fill="x")
else:
Label(self, text="", **self.style.text).pack(fill="x")

def _get_objs(self):
master = self.winfo_toplevel()
if hasattr(master, "get_widgets"):
include = self.style_def.get("include", ())
exclude = self.style_def.get("exclude", ())
objs = master.get_widgets()
if include:
objs = list(filter(lambda x: isinstance(x, tuple(include)), objs))
if exclude:
objs = list(filter(lambda x: not isinstance(x, tuple(exclude)), objs))
return objs
return []

def set_up(self):
objs = self._get_objs()
self._spinner.set_item_class(Widget.WidgetChoiceItem)
self._spinner.set_values((
'', *objs,
))

def set(self, value):
print(value)
if isinstance(value, tk.Widget):
self._spinner.set(value)
return

# Override default conversion of value to string by Choice class
widget = list(filter(lambda x: x.id == value, self._get_objs()))
# if widget does not match anything in the obj list presume as empty
value = widget[0] if widget else ''
self._spinner.set(value)


def get_editor(parent, definition):
if "compose" in definition:
type_ = "Compose"
Expand Down

0 comments on commit a5044a6

Please sign in to comment.