Skip to content

Commit

Permalink
Merge pull request #98 from Carifio24/improve-ui-widgets
Browse files Browse the repository at this point in the history
Improve UI widgets
  • Loading branch information
Carifio24 authored Dec 22, 2024
2 parents c381c5f + 1b760ed commit fb75832
Show file tree
Hide file tree
Showing 15 changed files with 609 additions and 239 deletions.
9 changes: 8 additions & 1 deletion glue_ar/common/scatter_export_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@


class ARVispyScatterExportOptions(State):
resolution = RangedCallbackProperty(default=10, min_value=3, max_value=50, resolution=1)
resolution = RangedCallbackProperty(
default=10,
min_value=3,
max_value=50,
resolution=1,
docstring="Controls the resolution of the sphere meshes used for scatter points. "
"Higher means better resolution, but a larger filesize.",
)


class ARIpyvolumeScatterExportOptions(State):
Expand Down
6 changes: 3 additions & 3 deletions glue_ar/common/tests/test_base_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@


class DummyState(State):
cb_int = CallbackProperty(2)
cb_float = CallbackProperty(0.7)
cb_bool = CallbackProperty(False)
cb_int = CallbackProperty(2, docstring="Integer callback property")
cb_float = CallbackProperty(0.7, docstring="Float callback property")
cb_bool = CallbackProperty(False, docstring="Boolean callback property")


class BaseExportDialogTest:
Expand Down
25 changes: 22 additions & 3 deletions glue_ar/common/volume_export_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,28 @@


class ARIsosurfaceExportOptions(State):
isosurface_count = RangedCallbackProperty(default=20, min_value=1, max_value=50)
isosurface_count = RangedCallbackProperty(
default=20,
min_value=1,
max_value=50,
docstring="The number of isosurfaces used in the export.",
)


class ARVoxelExportOptions(State):
opacity_cutoff = RangedCallbackProperty(default=0.1, min_value=0.01, max_value=1, resolution=0.01)
opacity_resolution = RangedCallbackProperty(default=0.02, min_value=0.005, max_value=1, resolution=0.005)
opacity_cutoff = RangedCallbackProperty(
default=0.1,
min_value=0.01,
max_value=1,
resolution=0.01,
docstring="The minimum opacity voxels to retain. Voxels with a lower opacity will be "
"omitted from the export.",
)
opacity_resolution = RangedCallbackProperty(
default=0.02,
min_value=0.005,
max_value=1,
resolution=0.005,
docstring="The resolution of the opacity in the exported figure. Opacity values will be "
"rounded to the nearest integer multiple of this value.",
)
55 changes: 15 additions & 40 deletions glue_ar/jupyter/export_dialog.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import ipyvuetify as v # noqa
from ipyvuetify.VuetifyTemplate import VuetifyTemplate
from ipywidgets import DOMWidget, widget_serialization
from ipywidgets import widget_serialization
import traitlets
from typing import Callable, List, Optional
from typing import Callable, Optional

from echo import HasCallbackProperties
from glue.core.state_objects import State
from glue.viewers.common.viewer import Viewer
from glue_jupyter.link import link
from glue_jupyter.vuetify_helpers import link_glue_choices

from glue_ar.common.export_dialog_base import ARExportDialogBase
from glue_ar.jupyter.widgets import widgets_for_callback_property


class JupyterARExportDialog(ARExportDialogBase, VuetifyTemplate):
Expand All @@ -31,7 +31,7 @@ class JupyterARExportDialog(ARExportDialogBase, VuetifyTemplate):
method_items = traitlets.List().tag(sync=True)
method_selected = traitlets.Int().tag(sync=True)

layer_layout = traitlets.Instance(v.Container).tag(sync=True, **widget_serialization)
layer_layout = traitlets.Instance(v.Col).tag(sync=True, **widget_serialization)
has_layer_options = traitlets.Bool().tag(sync=True)

modelviewer = traitlets.Bool(True).tag(sync=True)
Expand All @@ -42,8 +42,9 @@ def __init__(self,
display: Optional[bool] = False,
on_cancel: Optional[Callable] = None,
on_export: Optional[Callable] = None):

ARExportDialogBase.__init__(self, viewer=viewer)
self.layer_layout = v.Container()
self.layer_layout = v.Col()
VuetifyTemplate.__init__(self)

self._on_layer_change(self.state.layer)
Expand All @@ -65,12 +66,17 @@ def _update_layer_ui(self, state: State):
for widget in self.layer_layout.children:
widget.close()

widgets = []
rows = []
input_widgets = []
self.layer_layout = v.Col()
for property, _ in state.iter_callback_properties():
name = self.display_name(property)
widgets.extend(self.widgets_for_property(state, property, name))
self.input_widgets = [w for w in widgets if isinstance(w, v.Slider)]
self.layer_layout = v.Container(children=widgets, px_0=True, py_0=True)
widgets = widgets_for_callback_property(state, property, name)
input_widgets.extend(w for w in widgets if isinstance(w, v.Slider))
rows.append(v.Row(children=widgets, align="center"))

self.layer_layout.children = rows
self.input_widgets = input_widgets
self.has_layer_options = len(self.layer_layout.children) > 0

def _on_method_change(self, method_name: str):
Expand All @@ -84,37 +90,6 @@ def _on_filetype_change(self, filetype: str):
self.show_compression = gl
self.show_modelviewer = gl

def widgets_for_property(self,
instance: HasCallbackProperties,
property: str,
display_name: str) -> List[DOMWidget]:

value = getattr(instance, property)
t = type(value)
if t is bool:
widget = v.Checkbox(label=display_name)
link((instance, property), (widget, 'value'))
return [widget]
elif t in (int, float):
instance_type = type(instance)
cb_property = getattr(instance_type, property)
min = getattr(cb_property, 'min_value', 1 if t is int else 0.01)
max = getattr(cb_property, 'max_value', 100 * min)
step = getattr(cb_property, 'resolution', None)
if step is None:
step = 1 if t is int else 0.01
widget = v.Slider(min=min,
max=max,
step=step,
label=display_name,
thumb_label=f"{value:g}")
link((instance, property),
(widget, 'v_model'))

return [widget]
else:
return []

def vue_cancel_dialog(self, *args):
self.state_dictionary = {}
self.dialog_open = False
Expand Down
25 changes: 0 additions & 25 deletions glue_ar/jupyter/tests/test_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
# We can't use the Jupyter vispy widget for these tests until
# https://github.com/glue-viz/glue-vispy-viewers/pull/388 is released
from glue_jupyter.ipyvolume.volume import IpyvolumeVolumeView
from ipyvuetify import Checkbox, Slider

from glue_ar.common.tests.test_base_dialog import BaseExportDialogTest, DummyState
from glue_ar.jupyter.export_dialog import JupyterARExportDialog
Expand Down Expand Up @@ -89,30 +88,6 @@ def test_filetype_change(self):
assert self.dialog.show_compression
assert self.dialog.show_modelviewer

def test_widgets_for_property(self):
state = DummyState()

int_widgets = self.dialog.widgets_for_property(state, "cb_int", "Int CB")
assert len(int_widgets) == 1
widget = int_widgets[0]
assert isinstance(widget, Slider)
assert widget.label == "Int CB"
assert widget.v_model == 2

float_widgets = self.dialog.widgets_for_property(state, "cb_float", "Float CB")
assert len(float_widgets) == 1
widget = float_widgets[0]
assert isinstance(widget, Slider)
assert widget.label == "Float CB"
assert widget.v_model == 0.7

bool_widgets = self.dialog.widgets_for_property(state, "cb_bool", "Bool CB")
assert len(bool_widgets) == 1
widget = bool_widgets[0]
assert isinstance(widget, Checkbox)
assert widget.label == "Bool CB"
assert widget.value is False

def test_update_layer_ui(self):
state = DummyState()
self.dialog._update_layer_ui(state)
Expand Down
100 changes: 100 additions & 0 deletions glue_ar/jupyter/tests/test_widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from pytest import importorskip

importorskip("glue_jupyter")

from echo import CallbackProperty
from ipyvuetify import Checkbox, Img, Slider, Tooltip

from glue_ar.common.tests.test_base_dialog import DummyState
from glue_ar.jupyter.widgets import boolean_callback_widgets, info_icon, \
info_tooltip, number_callback_widgets, \
widgets_for_callback_property


def test_info_tooltip():
assert info_tooltip(DummyState.cb_int) == ["Integer callback property"]
assert info_tooltip(DummyState.cb_float) == ["Float callback property"]
assert info_tooltip(DummyState.cb_bool) == ["Boolean callback property"]


def test_info_button():
state = DummyState()
for property in state.callback_properties():
cb_property: CallbackProperty = getattr(DummyState, property)
icon = info_icon(cb_property)
assert isinstance(icon, Tooltip)
assert len(icon.children) == 1
assert len(icon.v_slots) == 1
slot = icon.v_slots[0]
assert slot["name"] == "activator"
assert slot["variable"] == "tooltip"
assert len(slot["children"]) == 1
img = slot["children"][0]
assert isinstance(img, Img)


def test_boolean_callback_widgets():
state = DummyState()
widgets = boolean_callback_widgets(state, "cb_bool", "Bool CB")
assert len(widgets) == 2
checkbox, icon = widgets

assert isinstance(checkbox, Checkbox)
assert checkbox.label == "Bool CB"

assert not checkbox.value
assert isinstance(icon, Tooltip)


def test_integer_callback_widgets():
state = DummyState()
widgets = number_callback_widgets(state, "cb_int", "Int CB")
assert len(widgets) == 2
slider, icon = widgets

assert isinstance(slider, Slider)
assert slider.label == "Int CB"
assert slider.v_model == 2

assert isinstance(icon, Tooltip)


def test_float_callback_widgets():
state = DummyState()
widgets = number_callback_widgets(state, "cb_float", "Float CB")
assert len(widgets) == 2
slider, icon = widgets

assert isinstance(slider, Slider)
assert slider.label == "Float CB"
assert slider.v_model == 0.7

assert isinstance(icon, Tooltip)


def test_widgets_for_property():
state = DummyState()

int_widgets = widgets_for_callback_property(state, "cb_int", "Int CB")
assert len(int_widgets) == 2
slider, icon = int_widgets
assert isinstance(slider, Slider)
assert slider.label == "Int CB"
assert slider.v_model == 2
assert isinstance(icon, Tooltip)

float_widgets = widgets_for_callback_property(state, "cb_float", "Float CB")
assert len(float_widgets) == 2
slider, icon = float_widgets
assert isinstance(slider, Slider)
assert slider.label == "Float CB"
assert slider.v_model == 0.7
assert isinstance(icon, Tooltip)

bool_widgets = widgets_for_callback_property(state, "cb_bool", "Bool CB")
assert len(bool_widgets) == 2
checkbox, icon = bool_widgets
assert isinstance(checkbox, Checkbox)
assert checkbox.label == "Bool CB"
assert not checkbox.value
assert isinstance(icon, Tooltip)
Loading

0 comments on commit fb75832

Please sign in to comment.