diff --git a/glue_ar/common/scatter_export_options.py b/glue_ar/common/scatter_export_options.py
index 4cc1db9..5caf181 100644
--- a/glue_ar/common/scatter_export_options.py
+++ b/glue_ar/common/scatter_export_options.py
@@ -6,9 +6,7 @@
class ARVispyScatterExportOptions(State):
-
- theta_resolution = CallbackProperty(10)
- phi_resolution = CallbackProperty(10)
+ resolution = CallbackProperty(10)
class ARIpyvolumeScatterExportOptions(State):
diff --git a/glue_ar/common/scatter_gltf.py b/glue_ar/common/scatter_gltf.py
index 3dc75e3..1cdb2c9 100644
--- a/glue_ar/common/scatter_gltf.py
+++ b/glue_ar/common/scatter_gltf.py
@@ -350,8 +350,9 @@ def add_vispy_scatter_layer_gltf(builder: GLTFBuilder,
bounds: Bounds,
clip_to_bounds: bool = True):
- theta_resolution = int(options.theta_resolution)
- phi_resolution = int(options.phi_resolution)
+ resolution = int(options.resolution)
+ theta_resolution = resolution
+ phi_resolution = resolution
triangles = sphere_triangles(theta_resolution=theta_resolution,
phi_resolution=phi_resolution)
diff --git a/glue_ar/common/scatter_stl.py b/glue_ar/common/scatter_stl.py
index 524d983..46de629 100644
--- a/glue_ar/common/scatter_stl.py
+++ b/glue_ar/common/scatter_stl.py
@@ -59,8 +59,9 @@ def add_vispy_scatter_layer_stl(builder: STLBuilder,
bounds: Bounds,
clip_to_bounds: bool = True):
- theta_resolution = int(options.theta_resolution)
- phi_resolution = int(options.phi_resolution)
+ resolution = int(options.resolution)
+ theta_resolution = resolution
+ phi_resolution = resolution
triangles = sphere_triangles(theta_resolution=theta_resolution,
phi_resolution=phi_resolution)
diff --git a/glue_ar/common/scatter_usd.py b/glue_ar/common/scatter_usd.py
index 0526783..8d35fca 100644
--- a/glue_ar/common/scatter_usd.py
+++ b/glue_ar/common/scatter_usd.py
@@ -195,8 +195,9 @@ def add_vispy_scatter_layer_usd(builder: USDBuilder,
bounds: Bounds,
clip_to_bounds: bool = True):
- theta_resolution = int(options.theta_resolution)
- phi_resolution = int(options.phi_resolution)
+ resolution = int(options.resolution)
+ theta_resolution = resolution
+ phi_resolution = resolution
triangles = sphere_triangles(theta_resolution=theta_resolution,
phi_resolution=phi_resolution)
diff --git a/glue_ar/common/tests/test_base_dialog.py b/glue_ar/common/tests/test_base_dialog.py
index 1033218..1ed3081 100644
--- a/glue_ar/common/tests/test_base_dialog.py
+++ b/glue_ar/common/tests/test_base_dialog.py
@@ -9,8 +9,8 @@
class DummyState(State):
- cb_int = CallbackProperty(0)
- cb_float = CallbackProperty(1.7)
+ cb_int = CallbackProperty(2)
+ cb_float = CallbackProperty(0.7)
cb_bool = CallbackProperty(False)
diff --git a/glue_ar/common/tests/test_scatter_gltf.py b/glue_ar/common/tests/test_scatter_gltf.py
index 29ac78e..dabc985 100644
--- a/glue_ar/common/tests/test_scatter_gltf.py
+++ b/glue_ar/common/tests/test_scatter_gltf.py
@@ -62,8 +62,8 @@ def test_basic_export(self, app_type: str, viewer_type: str):
# TODO: 3 is the value for ipyvolume's diamond, which is the ipv default
# But we should make this more robust
- theta_resolution: int = getattr(options, "theta_resolution", 3)
- phi_resolution: int = getattr(options, "phi_resolution", 3)
+ theta_resolution: int = getattr(options, "resolution", 3)
+ phi_resolution: int = getattr(options, "resolution", 3)
triangles_count = sphere_triangles_count(theta_resolution=theta_resolution,
phi_resolution=phi_resolution)
points_count = sphere_points_count(theta_resolution=theta_resolution,
diff --git a/glue_ar/common/tests/test_scatter_stl.py b/glue_ar/common/tests/test_scatter_stl.py
index d2c3ea1..e6e14b9 100644
--- a/glue_ar/common/tests/test_scatter_stl.py
+++ b/glue_ar/common/tests/test_scatter_stl.py
@@ -50,8 +50,8 @@ def test_basic_export(self, app_type: str, viewer_type: str):
# TODO: 3 is the value for ipyvolume's diamond, which is the ipv default
# But we should make this more robust
- theta_resolution: int = getattr(options, "theta_resolution", 3)
- phi_resolution: int = getattr(options, "phi_resolution", 3)
+ theta_resolution: int = getattr(options, "resolution", 3)
+ phi_resolution: int = getattr(options, "resolution", 3)
points_count = sphere_points_count(theta_resolution, phi_resolution)
triangle_count = sphere_triangles_count(theta_resolution, phi_resolution)
for index in range(self.data1.size):
diff --git a/glue_ar/common/tests/test_scatter_usd.py b/glue_ar/common/tests/test_scatter_usd.py
index 25ccdcd..7c8e668 100644
--- a/glue_ar/common/tests/test_scatter_usd.py
+++ b/glue_ar/common/tests/test_scatter_usd.py
@@ -40,8 +40,8 @@ def test_basic_export(self, app_type: str, viewer_type: str):
_, options = self.state_dictionary[label]
# The default ipyvolume geometry type is diamond
- theta_resolution: int = getattr(options, "theta_resolution", 3)
- phi_resolution: int = getattr(options, "phi_resolution", 3)
+ theta_resolution: int = getattr(options, "resolution", 3)
+ phi_resolution: int = getattr(options, "resolution", 3)
sphere_pts_count = sphere_points_count(theta_resolution=theta_resolution, phi_resolution=phi_resolution)
sphere_tris_count = sphere_triangles_count(theta_resolution=theta_resolution, phi_resolution=phi_resolution)
expected_vert_cts = [3] * sphere_tris_count * self.n
diff --git a/glue_ar/jupyter/export_dialog.py b/glue_ar/jupyter/export_dialog.py
index 0044e0d..b8f1db3 100644
--- a/glue_ar/jupyter/export_dialog.py
+++ b/glue_ar/jupyter/export_dialog.py
@@ -4,7 +4,7 @@
import traitlets
from typing import Callable, List, Optional
-from echo import HasCallbackProperties
+from echo import HasCallbackProperties, add_callback
from glue.core.state_objects import State
from glue.viewers.common.viewer import Viewer
from glue_jupyter.link import link
@@ -13,40 +13,6 @@
from glue_ar.common.export_dialog_base import ARExportDialogBase
-# Based on https://github.com/widgetti/ipyvuetify/issues/241
-class NumberField(v.VuetifyTemplate):
- label = traitlets.Unicode().tag(sync=True)
- value = traitlets.Unicode().tag(sync=True)
-
- temp_error = traitlets.Unicode(allow_none=True, default_value=None).tag(sync=True)
-
- def __init__(self, type, label, error_message, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.number_type = type
- self.label = label
- self.error_message = error_message
-
- @traitlets.default("template")
- def _template(self):
- return """
-
-
- """
-
- def vue_temp_rule(self, value):
- self.temp_error = None
- try:
- self.number_type(value)
- except ValueError:
- self.temp_error = self.error_message
-
-
class JupyterARExportDialog(ARExportDialogBase, VuetifyTemplate):
template_file = (__file__, "export_dialog.vue")
@@ -103,7 +69,7 @@ def _update_layer_ui(self, state: State):
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, NumberField)]
+ 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)
self.has_layer_options = len(self.layer_layout.children) > 0
@@ -130,12 +96,21 @@ def widgets_for_property(self,
link((instance, property), (widget, 'value'))
return [widget]
elif t in (int, float):
- name = "integer" if t is int else "number"
- widget = NumberField(type=t, label=display_name, error_message=f"You must enter a valid {name}")
+ step = 0.01 if t is float else 1
+ min = step
+ max = min * 100
+ widget = v.Slider(min=min,
+ max=max,
+ step=step,
+ label=display_name,
+ thumb_label=f"{value:g}")
link((instance, property),
- (widget, 'value'),
- lambda value: str(value),
- lambda text: t(text))
+ (widget, 'value'))
+
+ def update_label(value):
+ widget.thumb_label = f"{value:g}"
+ add_callback(instance, property, update_label)
+
return [widget]
else:
return []
@@ -147,7 +122,7 @@ def vue_cancel_dialog(self, *args):
self.on_cancel()
def vue_export_viewer(self, *args):
- okay = all(widget.temp_error is None for widget in self.input_widgets)
+ okay = all(not widget.error for widget in self.input_widgets)
if not okay:
return
self.dialog_open = False
diff --git a/glue_ar/jupyter/number_field.py b/glue_ar/jupyter/number_field.py
new file mode 100644
index 0000000..101c25a
--- /dev/null
+++ b/glue_ar/jupyter/number_field.py
@@ -0,0 +1,36 @@
+import traitlets
+import ipyvuetify as v
+
+
+# Based on https://github.com/widgetti/ipyvuetify/issues/241
+class NumberField(v.VuetifyTemplate):
+ label = traitlets.Unicode().tag(sync=True)
+ value = traitlets.Unicode().tag(sync=True)
+
+ temp_error = traitlets.Unicode(allow_none=True, default_value=None).tag(sync=True)
+
+ def __init__(self, type, label, error_message, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.number_type = type
+ self.label = label
+ self.error_message = error_message
+
+ @traitlets.default("template")
+ def _template(self):
+ return """
+
+
+ """
+
+ def vue_temp_rule(self, value):
+ self.temp_error = None
+ try:
+ self.number_type(value)
+ except ValueError:
+ self.temp_error = self.error_message
diff --git a/glue_ar/jupyter/tests/test_dialog.py b/glue_ar/jupyter/tests/test_dialog.py
index e2fd098..f6ea399 100644
--- a/glue_ar/jupyter/tests/test_dialog.py
+++ b/glue_ar/jupyter/tests/test_dialog.py
@@ -8,10 +8,10 @@
# 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
+from ipyvuetify import Checkbox, Slider
from glue_ar.common.tests.test_base_dialog import BaseExportDialogTest, DummyState
-from glue_ar.jupyter.export_dialog import JupyterARExportDialog, NumberField
+from glue_ar.jupyter.export_dialog import JupyterARExportDialog
class TestJupyterExportDialog(BaseExportDialogTest):
@@ -95,20 +95,16 @@ def test_widgets_for_property(self):
int_widgets = self.dialog.widgets_for_property(state, "cb_int", "Int CB")
assert len(int_widgets) == 1
widget = int_widgets[0]
- assert isinstance(widget, NumberField)
+ assert isinstance(widget, Slider)
assert widget.label == "Int CB"
- assert widget.value == "0"
- assert widget.number_type is int
- assert widget.error_message == "You must enter a valid integer"
+ assert widget.value == 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, NumberField)
+ assert isinstance(widget, Slider)
assert widget.label == "Float CB"
- assert widget.value == "1.7"
- assert widget.number_type is float
- assert widget.error_message == "You must enter a valid number"
+ assert widget.value == 0.7
bool_widgets = self.dialog.widgets_for_property(state, "cb_bool", "Bool CB")
assert len(bool_widgets) == 1
diff --git a/glue_ar/qt/export_dialog.py b/glue_ar/qt/export_dialog.py
index 2668e3b..931bba2 100644
--- a/glue_ar/qt/export_dialog.py
+++ b/glue_ar/qt/export_dialog.py
@@ -1,14 +1,14 @@
import os
from typing import List
-from echo import HasCallbackProperties
-from echo.qt import autoconnect_callbacks_to_qt, connect_checkable_button, connect_float_text
+from echo import HasCallbackProperties, add_callback
+from echo.qt import autoconnect_callbacks_to_qt, connect_checkable_button, connect_value
from glue.core.state_objects import State
from glue_qt.utils import load_ui
from glue_ar.common.export_dialog_base import ARExportDialogBase
-from qtpy.QtWidgets import QCheckBox, QDialog, QFormLayout, QHBoxLayout, QLabel, QLayout, QLineEdit, QWidget
-from qtpy.QtGui import QIntValidator, QDoubleValidator
+from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QCheckBox, QDialog, QFormLayout, QHBoxLayout, QLabel, QLayout, QSizePolicy, QSlider, QWidget
__all__ = ['QtARExportDialog']
@@ -46,12 +46,27 @@ def _widgets_for_property(self,
label = QLabel()
prompt = f"{display_name}:"
label.setText(prompt)
- widget = QLineEdit()
- validator = QIntValidator() if t is int else QDoubleValidator()
- widget.setText(str(value))
- widget.setValidator(validator)
- self._layer_connections.append(connect_float_text(instance, property, widget))
- return [label, widget]
+ widget = QSlider()
+ policy = QSizePolicy()
+ policy.setHorizontalPolicy(QSizePolicy.Policy.Expanding)
+ policy.setVerticalPolicy(QSizePolicy.Policy.Fixed)
+ widget.setOrientation(Qt.Orientation.Horizontal)
+ widget.setMinimum(1)
+ widget.setMaximum(100)
+
+ widget.setSizePolicy(policy)
+
+ value_label = QLabel()
+
+ def update_label(value):
+ value_label.setText(f"{value:g}")
+
+ update_label(value)
+ add_callback(instance, property, update_label)
+
+ range = (1, 100) if t is int else (0.01, 1)
+ self._layer_connections.append(connect_value(instance, property, widget, value_range=range))
+ return [label, widget, value_label]
else:
return []
diff --git a/glue_ar/qt/tests/test_dialog.py b/glue_ar/qt/tests/test_dialog.py
index 629478e..8c94cf7 100644
--- a/glue_ar/qt/tests/test_dialog.py
+++ b/glue_ar/qt/tests/test_dialog.py
@@ -6,8 +6,7 @@
from glue_qt.app import GlueApplication
from glue_vispy_viewers.volume.qt.volume_viewer import VispyVolumeViewer
-from qtpy.QtGui import QDoubleValidator, QIntValidator
-from qtpy.QtWidgets import QCheckBox, QLabel, QLineEdit
+from qtpy.QtWidgets import QCheckBox, QLabel, QSlider
from glue_ar.common.tests.test_base_dialog import BaseExportDialogTest, DummyState
from glue_ar.common.scatter_export_options import ARVispyScatterExportOptions
@@ -77,22 +76,24 @@ def test_widgets_for_property(self):
state = DummyState()
int_widgets = self.dialog._widgets_for_property(state, "cb_int", "Int CB")
- assert len(int_widgets) == 2
- label, edit = int_widgets
+ assert len(int_widgets) == 3
+ label, slider, value_label = int_widgets
assert isinstance(label, QLabel)
assert label.text() == "Int CB:"
- assert isinstance(edit, QLineEdit)
- assert isinstance(edit.validator(), QIntValidator)
- assert edit.text() == "0"
+ assert isinstance(slider, QSlider)
+ assert slider.value() == 2
+ assert isinstance(value_label, QLabel)
+ assert value_label.text() == "2"
float_widgets = self.dialog._widgets_for_property(state, "cb_float", "Float CB")
- assert len(float_widgets) == 2
- label, edit = float_widgets
+ assert len(float_widgets) == 3
+ label, slider, value_label = float_widgets
assert isinstance(label, QLabel)
assert label.text() == "Float CB:"
- assert isinstance(edit, QLineEdit)
- assert isinstance(edit.validator(), QDoubleValidator)
- assert edit.text() == "1.7"
+ assert isinstance(slider, QSlider)
+ assert slider.value() == 70
+ assert isinstance(value_label, QLabel)
+ assert value_label.text() == "0.7"
bool_widgets = self.dialog._widgets_for_property(state, "cb_bool", "Bool CB")
assert len(bool_widgets) == 1
@@ -108,7 +109,7 @@ def test_update_layer_ui(self):
state = ARVispyScatterExportOptions()
self.dialog._update_layer_ui(state)
- assert self.dialog.ui.layer_layout.rowCount() == 2
+ assert self.dialog.ui.layer_layout.rowCount() == 1
def test_clear_layout(self):
self.dialog._clear_layer_layout()
diff --git a/glue_ar/qt/tests/test_tool_scatter.py b/glue_ar/qt/tests/test_tool_scatter.py
index c67cb82..e7460e8 100644
--- a/glue_ar/qt/tests/test_tool_scatter.py
+++ b/glue_ar/qt/tests/test_tool_scatter.py
@@ -95,5 +95,4 @@ def test_tool_export_call(self, extension, compression):
assert len(value) == 2
assert value[0] == "Scatter"
assert isinstance(value[1], ARVispyScatterExportOptions)
- assert value[1].theta_resolution == 10
- assert value[1].phi_resolution == 10
+ assert value[1].resolution == 10
diff --git a/glue_ar/qt/tests/test_tool_volume.py b/glue_ar/qt/tests/test_tool_volume.py
index 542593e..294c212 100644
--- a/glue_ar/qt/tests/test_tool_volume.py
+++ b/glue_ar/qt/tests/test_tool_volume.py
@@ -99,8 +99,7 @@ def test_tool_export_call(self, extension, compression):
scatter_method, scatter_state = state_dict["Scatter Data"]
assert scatter_method == "Scatter"
assert isinstance(scatter_state, ARVispyScatterExportOptions)
- assert scatter_state.theta_resolution == 10
- assert scatter_state.phi_resolution == 10
+ assert scatter_state.resolution == 10
volume_method, volume_state = state_dict["Volume Data"]
assert volume_method == "Isosurface"
diff --git a/glue_ar/tests/test_utils.py b/glue_ar/tests/test_utils.py
index abd91b1..aeaa2ae 100644
--- a/glue_ar/tests/test_utils.py
+++ b/glue_ar/tests/test_utils.py
@@ -162,6 +162,8 @@ def test_clip_sides_native():
assert clip_sides(viewer_state, clip_size=clip_size) == (max_size, max_size / 2, max_size / 4)
+@pytest.mark.skipif(not (GLUE_QT_INSTALLED or GLUE_JUPYTER_INSTALLED),
+ reason="Requires either glue-qt or glue-jupyter to create application")
def test_mask_for_bounds():
x_values = range(10, 25)
y_values = range(130, 145)