class Node(Widget, ItemWidget): """A :class:`.NodeEditor` node. Should only contain :class:`.NodeAttribute` objects, any other kind of widget will not be displayed. Note that :class:`.NodeAttribute` objects may contain any kind or number of widget though.""" label: str = ConfigProperty() draggable: bool = ConfigProperty() def __init__(self, label: str = None, *, name_id: str = None, **config): super().__init__(label=label, name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_node(self.id, **dpg_args) def __enter__(self) -> Node: return self def __exit__(self, exc_type, exc_val, exc_tb): dpgcore.end()
class TabButton(Widget, ItemWidget): """A button that can be added to a :class:`TabBar`. Note: This widget must be placed inside a :class:`.TabBar` to be visible. """ label: str = ConfigProperty() #: Create a button on the tab that can hide the tab. closable: bool = ConfigProperty() order_mode: TabOrderMode @ConfigProperty() def order_mode(self) -> TabOrderMode: config = self.get_config() if config.get('leading'): return TabOrderMode.Leading if config.get('trailing'): return TabOrderMode.Trailing if config.get('no_reorder'): return TabOrderMode.Fixed return TabOrderMode.Reorderable @order_mode.getconfig def order_mode(self, value: TabOrderMode): return { mode.value: (mode == value) for mode in TabOrderMode if mode.value is not None } #: Disable tooltip no_tooltip: bool = ConfigProperty() def __init__(self, label: str = None, *, name_id: str = None, **config): super().__init__(label=label, name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_tab_button(self.id, **dpg_args)
class TabItem(Widget, ItemWidgetMx, ContainerWidgetMx['TabItem']): """A container whose contents will be displayed when selected in a :class:`.TabBar`. Note: This widget must be placed inside a :class:`.TabBar` to be visible. """ label: str = ConfigProperty() #: Create a button on the tab that can hide the tab. closable: bool = ConfigProperty() order_mode: TabOrderMode @ConfigProperty() def order_mode(self) -> TabOrderMode: config = self.get_config() if config.get('leading'): return TabOrderMode.Leading if config.get('trailing'): return TabOrderMode.Trailing if config.get('no_reorder'): return TabOrderMode.Fixed return TabOrderMode.Reorderable @order_mode.getconfig def order_mode(self, value: TabOrderMode): return { mode.value: (mode == value) for mode in TabOrderMode if mode.value is not None } #: Disable tooltip no_tooltip: bool = ConfigProperty() def __init__(self, label: str = None, **config): super().__init__(label=label, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_tab(self.id, **dpg_args)
class IndentLayout(Widget, ItemWidgetMx, ContainerWidgetMx['IndentLayout']): """Adds an indent to contained items.""" offset: float = ConfigProperty() def __init__(self, **config): super().__init__(**config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_indent(name=self.id, **dpg_args) def __finalize__(self) -> None: dpgcore.unindent() # doesn't use dpgcore.end()
class Menu(Widget, ItemWidgetMx, ContainerWidgetMx['Menu']): """A menu containing :class:`.MenuItem` objects. While they are often found inside a :class:`.MenuBar`, they are actually a general container that can be added anywhere and contain other kinds of widgets (e.g. buttons and text), even if it is unusual.""" label: str = ConfigProperty() def __init__(self, label: str = None, **config): super().__init__(label=label, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_menu(self.id, **dpg_args)
class MenuItem(Widget, ItemWidget): """An item for a :class:`.Menu`.""" label: str = ConfigProperty() #: Keyboard shortcut, e.g. `'CTRL+M'`. shortcut: str = ConfigProperty() #: If ``True``, a checkmark is shown if the item's :attr:`value` is ``True``. enable_check: bool = ConfigProperty(key='check') def __init__(self, label: str = None, value: bool = None, *, name_id: str = None, **config): super().__init__(label=label, name_id=name_id, **config) if value is not None: self.value = value def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_menu_item(self.id, **dpg_args)
class TabBar(Widget, ItemWidgetMx, ContainerWidgetMx['TabBar']): """A container that allows switching between different tabs. Note: This container should only contain :class:`.TabItem` or :class:`.TabButton` elements. """ reorderable: bool = ConfigProperty() def __init__(self, **config): super().__init__(**config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_tab_bar(self.id, **dpg_args)
class RadioButtons(Widget, ItemWidget, ValueWidget[int], MutableSequence[str]): """A set of radio buttons. This widget can be used as a mutable sequence of labels. Changing the sequence will change the radio buttons in the group and their labels.""" value: int #: The **index** of the selected item. horizontal: bool = ConfigProperty() items: Sequence[str] @ConfigProperty() def items(self) -> Sequence[str]: """Get or set this widget's items as a sequence.""" return tuple(self._get_items()) @items.getconfig def items(self, items: Sequence[str]): return {'items':list(items)} def __init__(self, items: Iterable[str], value: int = 0, *, name_id: str = None, **config): super().__init__(items=items, default_value=value, name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_radio_button(self.id, **dpg_args) def _get_items(self) -> List[str]: return self.get_config()['items'] def __len__(self) -> int: return len(self._get_items()) def __getitem__(self, idx: int) -> str: return self._get_items()[idx] def __setitem__(self, idx: int, label: str) -> None: items = self._get_items() items[idx] = label self.set_config(items=items) def __delitem__(self, idx: int) -> None: items = self._get_items() del items[idx] self.set_config(items=items) def insert(self, idx: int, label: str) -> None: items = self._get_items() items.insert(idx, label) self.set_config(items=items)
class ColorButton(Widget, ItemWidget): """A button that displays and enables copying of color data. Clicking and draging the color square will copy the color to be applied on any other color widget. While it has color "value", this is not a :class:`.ValueWidget`! """ color: ColorRGBA = ConfigPropertyColorRGBA( no_init=True) #: The color to copy on drag-and-drop. no_border: bool = ConfigProperty() no_alpha: bool = ConfigProperty() #: Don't include alpha channel. no_drag_drop: bool = ConfigProperty() def __init__(self, color: ColorRGBA = ColorRGBA(1, 0, 1), *, name_id: str = None, **config): super().__init__(color=dpg_export_color(color), name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_color_button(self.id, **dpg_args)
class LabelText(Widget, ItemWidgetMx, ValueWidgetMx[str]): """Display text with a label. Useful for output values when used with a :attr:`~.Widget.data_source`. The text is linked to the data source, while the label remains unchanged.""" value: str #: The text to display (separate from the :attr:`label`). label: str = ConfigProperty() color: ColorRGBA = ConfigPropertyColorRGBA() def __init__(self, label: str = None, value: str = '', **config): super().__init__(label=label, default_value=value, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_label_text(self.id, **dpg_args)
class LayoutIndent(Widget, ItemWidget): """Adds an indent to contained items.""" offset: float = ConfigProperty() def __init__(self, *, name_id: str = None, **config): super().__init__(name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_indent(name=self.id, **dpg_args) def __enter__(self) -> LayoutIndent: return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: dpgcore.unindent()
class NumberInput(Widget, ItemWidget, ValueWidget[_TInput], Generic[_TElem, _TInput]): """Base class for number input boxes.""" value: _TInput #: The inputted value. _default_value: _TInput format: str = ConfigProperty() on_enter: bool = ConfigProperty() step: _TElem = ConfigProperty() step_fast: _TElem = ConfigProperty() readonly: bool = ConfigProperty() label: str = ConfigProperty() min_value: Optional[_TElem] @ConfigProperty() def min_value(self) -> Optional[_TElem]: config = self.get_config() if not config.get('min_clamped'): return None return config['min_value'] @min_value.getconfig def min_value(self, value: Optional[_TElem]): if value is None: return {'min_clamped': False} return {'min_clamped': True, 'min_value': value} max_value: Optional[_TElem] @ConfigProperty() def max_value(self) -> Optional[_TElem]: config = self.get_config() if not config.get('max_clamped'): return None return config['max_value'] @max_value.getconfig def max_value(self, value: Optional[_TElem]): if value is None: return {'max_clamped': False} return {'max_clamped': True, 'max_value': value} def __init__(self, label: str = None, value: _TInput = None, *, name_id: str = None, **config): value = value or self._default_value super().__init__(label=label, default_value=value, name_id=name_id, **config)
class Popup(Widget): """A container that appears when a :class:`.ItemWidget` is interacted with.""" trigger: PopupInteraction #: The interaction that will trigger the popup. @ConfigProperty(key='mousebutton') def trigger(self) -> PopupInteraction: config = self.get_config() return PopupInteraction(config['mousebutton']) @trigger.getconfig def trigger(self, trigger: PopupInteraction): return {'mousebutton': trigger.value} #: Prevent the user from interacting with other windows until the popup is closed. modal: bool = ConfigProperty() def __init__(self, parent: ItemWidget, *, name_id: str = None, **config): self._popup_parent = parent super().__init__(name_id=name_id, **config) def __enter__(self) -> Popup: return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: dpgcore.end() def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_popup(self._popup_parent.id, self.id, **dpg_args) parent: ItemWidget @property def parent(self) -> ItemWidget: """The :class:`.ItemWidget` that the popup is attached to. Cannot be changed.""" return self._popup_parent def close(self) -> None: """Closes the popup. Node: Modal popups cannot be closed except by using this method. """ dpgcore.close_popup(self.id)
class TabBar(Widget, ItemWidget): """A container that allows switching between different tabs. Note: This container should only contain :class:`.TabItem` or :class:`.TabButton` elements. """ reorderable: bool = ConfigProperty() def __init__(self, *, name_id: str = None, **config): super().__init__(name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_tab_bar(self.id, **dpg_args) def __enter__(self) -> TabBar: return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: dpgcore.end()
class Menu(Widget, ItemWidget): """A menu containing :class:`.MenuItem` objects. While they are often found inside a :class:`.MenuBar`, they are actually a general container that can be added anywhere and contain other kinds of widgets (e.g. buttons and text), even if it is unusual.""" label: str = ConfigProperty() def __init__(self, label: str = None, *, name_id: str = None, **config): super().__init__(label=label, name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_menu(self.id, **dpg_args) def __enter__(self) -> Menu: return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: dpgcore.end()
class ChildView(Widget, ItemWidgetMx, ContainerWidgetMx['ChildView']): """Adds an embedded child window with optional scollbars.""" border: bool = ConfigProperty() autosize_x: bool = ConfigProperty() autosize_y: bool = ConfigProperty() menubar: bool = ConfigProperty() #: Disable scrollbars (can still scroll with mouse or programmatically). no_scrollbar: bool = ConfigProperty() #: Allow horizontal scrollbar to appear. horizontal_scrollbar: bool = ConfigProperty() def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_child(self.id, **dpg_args)
class SliderInput(Generic[_TElem, _TInput], Widget, ItemWidgetMx, ValueWidgetMx[_TInput]): """Base class for slider types.""" value: _TInput #: The inputted value. _default_value: _TInput label: str = ConfigProperty() min_value: _TElem = ConfigProperty() max_value: _TElem = ConfigProperty() format: str = ConfigProperty() #: number format vertical: bool = ConfigProperty() #: Control whether a value can be manually entered using CTRL+Click no_input: bool = ConfigProperty() #: Whether to clamp the value when using manual input. By default CTRL+Click allows going out of bounds. clamped: bool = ConfigProperty() def __init__(self, label: str = None, value: _TElem = None, **config): value = value or self._default_value super().__init__(label=label, default_value=value, **config)
class ChildView(Widget, ItemWidget): """Adds an embedded child window with optional scollbars.""" border: bool = ConfigProperty() autosize_x: bool = ConfigProperty() autosize_y: bool = ConfigProperty() menubar: bool = ConfigProperty() #: Disable scrollbars (can still scroll with mouse or programmatically). no_scrollbar: bool = ConfigProperty() #: Allow horizontal scrollbar to appear. horizontal_scrollbar: bool = ConfigProperty() def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_child(self.id, **dpg_args) def __enter__(self) -> ChildView: return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: dpgcore.end()
class ColorPicker(Widget, ItemWidgetMx, ValueWidgetMx[ColorRGBA]): """A color picking widget. Clicking and draging the color square will copy the color to be applied on any other color widget. Right-click allows the style of the color picker to be changed.""" value: ColorRGBA #: The picked color. label: str = ConfigProperty() no_alpha: bool = ConfigProperty() no_small_preview: bool = ConfigProperty() no_inputs: bool = ConfigProperty() no_tooltip: bool = ConfigProperty() no_label: bool = ConfigProperty() no_side_preview: bool = ConfigProperty() alpha_bar: bool = ConfigProperty() alpha_preview: bool = ConfigProperty() alpha_preview_half: bool = ConfigProperty() display_rgb: bool = ConfigProperty() display_hsv: bool = ConfigProperty() display_hex: bool = ConfigProperty() picker_hue_bar: bool = ConfigProperty() picker_hue_wheel: bool = ConfigProperty() input_rgb: bool = ConfigProperty() input_hsv: bool = ConfigProperty() color_format: ColorFormatMode @ConfigProperty() def color_format(self) -> ColorFormatMode: config = self.get_config() if config['floats'] and not config['uint8']: return ColorFormatMode.Float return ColorFormatMode.UInt8 @color_format.getconfig def color_format(self, value: ColorFormatMode): if value == ColorFormatMode.UInt8: return {'uint8': False, 'floats': True} if value == ColorFormatMode.Float: return {'uint8': True, 'floats': False} raise ValueError('invalid color format mode') def __init__(self, label: str = None, value: ColorRGBA = ColorRGBA(1, 0, 1), **config): super().__init__(label=label, default_value=export_color_to_dpg(value), **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_color_picker4(self.id, **dpg_args) def __get_value__(self) -> ColorRGBA: return import_color_from_dpg(super().__get_value__()) def __set_value__(self, color: ColorRGBA) -> None: super().__set_value__(export_color_to_dpg(color))
class Plot(Widget, ItemWidgetMx): """A rich plot widget.""" ## Plot Axes xaxis: PlotXAxisConfig = PlotXAxis() #: The X-axis yaxis: PlotYAxisConfig = PlotYAxis(0) #: The Y-axis y2axis: PlotOptYAxisConfig = PlotYAxis(1, 'yaxis2') #: Optional Y-axis 2 y3axis: PlotOptYAxisConfig = PlotYAxis(2, 'yaxis3') #: Optional Y-axis 3 ## Config Properties label: str = ConfigProperty() x_axis_label: str = ConfigProperty(key='x_axis_name') y_axis_label: str = ConfigProperty(key='y_axis_name') show_annotations: bool = ConfigProperty() show_drag_lines: bool = ConfigProperty() show_drag_points: bool = ConfigProperty() show_color_scale: bool = ConfigProperty() scale_min: float = ConfigProperty() scale_max: float = ConfigProperty() scale_height: int = ConfigProperty() equal_aspects: bool = ConfigProperty() query: bool = ConfigProperty() crosshairs: bool = ConfigProperty() no_legend: bool = ConfigProperty() no_menus: bool = ConfigProperty() no_box_select: bool = ConfigProperty() no_mouse_pos: bool = ConfigProperty() no_highlight: bool = ConfigProperty() no_child: bool = ConfigProperty() anti_aliased: bool = ConfigProperty() def __init__(self, **config): # not super happy that we have to resort to typing.cast() here, but it works self._xaxis_config = PlotXAxisConfig(self, cast(PlotXAxis, Plot.xaxis)) self._yaxis_config = PlotYAxisConfig(self, cast(PlotYAxis, Plot.yaxis)) self._y2axis_config = PlotOptYAxisConfig(self, cast(PlotYAxis, Plot.y2axis)) self._y3axis_config = PlotOptYAxisConfig(self, cast(PlotYAxis, Plot.y3axis)) super().__init__(**config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_plot(self.id, **dpg_args) def add_dataseries(self, series: DataSeries, *, update_bounds: bool = True) -> None: """Add a :class:`.DataSeries` to this plot (or update it). Updates the data series if it has already been added.""" series.update(self, update_bounds) def remove_dataseries(self, series: DataSeries) -> None: """Remove a :class:`.DataSeries` from this plot if it has been added.""" dpgcore.delete_series(self.id, series.id) def clear(self) -> None: dpgcore.clear_plot(self.id) def set_xlimits(self, limits: Optional[PlotLimits]) -> None: """Set the ``(min, max)`` limits for the x-axis, or pass ``None`` to use automatic limits.""" if limits is None: dpgcore.set_plot_xlimits_auto(self.id) else: dpgcore.set_plot_xlimits(self.id, *limits) def set_ylimits(self, limits: Optional[PlotLimits]) -> None: """Set the ``(min, max)`` limits for the y-axis, or pass ``None`` to use automatic limits.""" if limits is None: dpgcore.set_plot_ylimits_auto(self.id) else: dpgcore.set_plot_ylimits(self.id, *limits) def set_xticks(self, ticks: Optional[Iterable[TickLabel]]) -> None: """Set the tick labels for the x-axis, or pass ``None`` to use automatic ticks.""" if ticks is None: dpgcore.reset_xticks(self.id) else: dpgcore.set_xticks(self.id, ticks) def set_yticks(self, ticks: Optional[Iterable[TickLabel]]) -> None: """Set the tick labels for the y-axis, or pass ``None`` to use automatic ticks.""" if ticks is None: dpgcore.reset_yticks(self.id) else: dpgcore.set_yticks(self.id, ticks) def add_annotation(self, text: str, pos: Tuple[float, float], offset: Tuple[float, float], *, color: ColorRGBA = None, clamped: bool = True) -> PlotAnnotation: """Creates a :class:`.PlotAnnotation` and adds it to the plot.""" return PlotAnnotation(self, text, pos, offset, color=color, clamped=clamped) def get_mouse_pos(self) -> Optional[Tuple[float, float]]: """Returns the ``(x, y)`` mouse position in the plot if it is hovered, or ``None``.""" if not self.is_hovered(): return None return dpgcore.get_plot_mouse_pos() def get_selected_query_area(self) -> Optional[PlotQueryArea]: """Returns a :class:`.PlotQueryArea` for the selected query area if there is one. A query area can be selected by the user by holding control and right-click dragging in a plot. If a query area has not been selected, this will return ``None``.""" if not dpgcore.is_plot_queried(self.id): return None return PlotQueryArea(*dpgcore.get_plot_query_area(self.id))
class ColorEdit(Widget, ItemWidget, ValueWidget[ColorRGBA]): """A color editing widget. Clicking and draging the color square will copy the color to be applied on any other color widget.""" value: ColorRGBA #: The inputted color. label: str = ConfigProperty() no_alpha: bool = ConfigProperty() #: Don't include alpha channel. no_picker: bool = ConfigProperty() no_options: bool = ConfigProperty() no_small_preview: bool = ConfigProperty() no_inputs: bool = ConfigProperty() no_tooltip: bool = ConfigProperty() no_label: bool = ConfigProperty() no_drag_drop: bool = ConfigProperty() alpha_bar: bool = ConfigProperty() alpha_preview: bool = ConfigProperty() alpha_preview_half: bool = ConfigProperty() display_rgb: bool = ConfigProperty() display_hsv: bool = ConfigProperty() display_hex: bool = ConfigProperty() input_rgb: bool = ConfigProperty() input_hsv: bool = ConfigProperty() color_format: ColorFormatMode @ConfigProperty() def color_format(self) -> ColorFormatMode: config = self.get_config() if config['floats'] and not config['uint8']: return ColorFormatMode.Float return ColorFormatMode.UInt8 @color_format.getconfig def color_format(self, value: ColorFormatMode): if value == ColorFormatMode.UInt8: return {'uint8': False, 'floats': True} if value == ColorFormatMode.Float: return {'uint8': True, 'floats': False} raise ValueError('invalid color format mode') def __init__(self, label: str = None, value: ColorRGBA = ColorRGBA(1, 0, 1), *, name_id: str = None, **config): super().__init__(label=label, default_value=dpg_export_color(value), name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_color_edit4(self.id, **dpg_args) def _get_value(self) -> ColorRGBA: return dpg_import_color(super()._get_value()) def _set_value(self, color: ColorRGBA) -> None: super()._set_value(dpg_export_color(color))
class ListBox(Widget, ItemWidgetMx, ValueWidgetMx[int], MutableSequence[str]): """A scrollable box containing a selection of items.""" value: int #: The **index** of the selected item. label: str = ConfigProperty() num_visible: int = ConfigProperty( key='num_items') #: The number of items to show. items: Sequence[str] @ConfigProperty() def items(self) -> Sequence[str]: """Get or set this widget's items as a sequence.""" return tuple(self._get_items()) @items.getconfig def items(self, items: Sequence[str]): return {'items': list(items)} def __init__(self, label: str = None, items: Iterable[str] = (), value: int = 0, **config): super().__init__(label=label, items=items, default_value=value, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_listbox(self.id, **dpg_args) def _get_items(self) -> List[str]: return self.get_config()['items'] def __len__(self) -> int: return len(self._get_items()) @overload def __getitem__(self, idx: int) -> str: ... @overload def __getitem__(self, idx: slice) -> Iterable[str]: ... def __getitem__(self, idx): return self._get_items()[idx] @overload def __setitem__(self, idx: int, label: str) -> None: ... @overload def __setitem__(self, idx: slice, label: Iterable[str]) -> None: ... def __setitem__(self, idx, label): items = self._get_items() items[idx] = label self.set_config(items=items) @overload def __delitem__(self, idx: int) -> None: ... @overload def __delitem__(self, idx: slice) -> None: ... def __delitem__(self, idx): items = self._get_items() del items[idx] self.set_config(items=items) def insert(self, idx: int, label: str) -> None: items = self._get_items() items.insert(idx, label) self.set_config(items=items)
class Combo(Widget, ItemWidgetMx, ValueWidgetMx[str], MutableSequence[str]): """A combo box (drop down). Unlike :class:`.RadioButtons`, the :attr:`value` of a Combo is one of the item strings, not the index. Unless specified, none of the items are initially selected and :attr:`value` is an empty string. """ value: str #: The string **value** of the selected item. label: str = ConfigProperty() popup_align_left: bool = ConfigProperty() no_arrow_button: bool = ConfigProperty( ) #: Don't display the arrow button. no_preview: bool = ConfigProperty( ) #: Don't display the preview box showing the selected item. items: Sequence[str] @ConfigProperty() def items(self) -> Sequence[str]: """Get or set this widget's items as a sequence.""" return tuple(self._get_items()) @items.getconfig def items(self, items: Sequence[str]): return {'items': list(items)} height_mode: ComboHeightMode @ConfigProperty(key='height') def height_mode(self) -> ComboHeightMode: config = self.get_config() for mode in ComboHeightMode: if config.get(mode.value): return mode warn('could not determine height_mode') return ComboHeightMode.Regular # its supposedly the default? @height_mode.getconfig def height_mode(self, value: ComboHeightMode) -> ItemConfigData: return {mode.value: (mode == value) for mode in ComboHeightMode} def __init__(self, label: str = None, items: Iterable[str] = (), value: str = '', **config): super().__init__(label=label, items=items, default_value=value, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_combo(self.id, **dpg_args) def _get_items(self) -> List[str]: return self.get_config()['items'] def __len__(self) -> int: return len(self._get_items()) @overload def __getitem__(self, idx: int) -> str: ... @overload def __getitem__(self, idx: slice) -> Iterable[str]: ... def __getitem__(self, idx): return self._get_items()[idx] @overload def __setitem__(self, idx: int, label: str) -> None: ... @overload def __setitem__(self, idx: slice, label: Iterable[str]) -> None: ... def __setitem__(self, idx, label): items = self._get_items() items[idx] = label self.set_config(items=items) @overload def __delitem__(self, idx: int) -> None: ... @overload def __delitem__(self, idx: slice) -> None: ... def __delitem__(self, idx): items = self._get_items() del items[idx] self.set_config(items=items) def insert(self, idx: int, label: str) -> None: items = self._get_items() items.insert(idx, label) self.set_config(items=items)
class Window(Widget): """Creates a new window.""" label: str = ConfigProperty() x_pos: int = ConfigProperty() y_pos: int = ConfigProperty() autosize: bool = ConfigProperty() no_resize: bool = ConfigProperty() no_title_bar: bool = ConfigProperty() no_move: bool = ConfigProperty() no_collapse: bool = ConfigProperty() no_focus_on_appearing: bool = ConfigProperty() no_bring_to_front_on_focus: bool = ConfigProperty() no_close: bool = ConfigProperty() no_background: bool = ConfigProperty() show_menubar: bool = ConfigProperty(key='menubar') #: Disable scrollbars (can still scroll with mouse or programmatically). no_scrollbar: bool = ConfigProperty() #: Allow horizontal scrollbar to appear. horizontal_scrollbar: bool = ConfigProperty() pos: Tuple[int, int] @ConfigProperty() def pos(self) -> Tuple[int, int]: """Get or set (x_pos, y_pos) as a tuple.""" config = self.get_config() return config['x_pos'], config['y_pos'] @pos.getconfig def pos(self, value: Tuple[int, int]) -> ItemConfigData: width, height = value return {'x_pos': width, 'y_pos': height} def __init__(self, label: str = None, *, name_id: str = None, **config): """ Parameters: label: window label. """ super().__init__(label=label, name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_window(self.id, on_close=self._on_close, **dpg_args) def __enter__(self) -> Window: return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: dpgcore.end() ## workaround for the fact that you can't set the on_close callback in DPG _on_close_callback: Optional[Callable] = None def _on_close(self, sender, data) -> None: if self._on_close_callback is not None: self._on_close_callback(sender, data) def on_close(self, callback: Optional[PyGuiCallback]) -> Callable: """Set on_close callback, can be used as a decorator.""" if callback is not None: callback = wrap_callback(callback) self._on_close_callback = callback return callback def resized(self, callback: PyGuiCallback) -> Callable: """Set resized callback, can be used as a decorator.""" dpgcore.set_resize_callback(wrap_callback(callback), handler=self.id) return callback
class Table(Widget, ItemWidgetMx): """Adds a simple table that can hold text. A Table's data consists of a sequence of rows, each row being a sequence of strings. Note that a Table has two different kinds of "columns". A Table will have a number of *data* columns and a number of *header* columns. These won't always match. If you have more data columns than header columns, only a subsection of the data will actually get shown. This will be the case even if :attr:`hide_headers` is ``True``. Parameters: headers: can be an iterable of header strings or an integer. If an integer is used, it will set the number of header columns and the :attr:`hide_headers` property will be set to ``True``. To get/set values in the table, indexing syntax can be used. For example: .. code-block:: python table[2, 3] = 'cell content' table[3, :] = ['sets', 'an', 'entire', 'row'] table[:, 4] = ['sets', 'an', 'entire', 'column'] table[:, :] = [['first', row'], ['second', 'row'], ['third', 'row]] Cell selection state can also be modified in a similar manner. .. code-block:: python table.selection[1, :] = True # selects the entire second row. """ hide_headers: bool = ConfigProperty( ) #: If ``True``, table headers will not be displayed. #: A :class:`.TableSelection` instance that can be used to get or modify the table's cell #: selection state. selected: TableSelection def __init__(self, headers: Union[int, Iterable[str]] = 2, **config: Any): if isinstance(headers, int): super().__init__(headers=['' for i in range(headers)], hide_headers=True, **config) else: super().__init__(headers=headers, **config) self.selected = TableSelection(self) def __setup_add_widget__(self, dpg_args: MutableMapping[str, Any]) -> None: dpgcore.add_table(self.id, **dpg_args) def set_headers(self, headers: Union[Iterable[str], int]) -> None: """Set the table headers. This determines the number of displayed columns (distinct from the number of data columns!). If an integer is passed, the headers will be replaced with empty strings and hidden.""" if isinstance(headers, int): headers = ['' for i in range(headers)] self.hide_headers = True dpgcore.set_headers(self.id, headers) def _get_data(self) -> List[List[str]]: return dpgcore.get_table_data(self.id) @property def rows(self) -> int: """The number of data rows.""" return len(self._get_data()) @property def columns(self) -> int: """The number of data columns.""" data = self._get_data() if len(data): return len(data[0]) return 0 @overload def __getitem__(self, indices: Tuple[int, int]) -> str: ... @overload def __getitem__(self, indices: Tuple[int, slice]) -> Sequence[str]: ... @overload def __getitem__(self, indices: Tuple[slice, int]) -> Sequence[str]: ... @overload def __getitem__(self, indices: Tuple[slice, slice]) -> Sequence[Sequence[str]]: ... def __getitem__(self, indices): """Get table data using indices or slices.""" row_idx, col_idx = indices if isinstance(row_idx, slice) and isinstance(col_idx, slice): return tuple( tuple(row[col_idx]) for row in self._get_data()[row_idx]) elif isinstance(row_idx, slice): return tuple(row[col_idx] for row in self._get_data()[row_idx]) elif isinstance(col_idx, slice): return tuple(self._get_data()[row_idx][col_idx]) else: return dpgcore.get_table_item(self.id, row_idx, col_idx) @overload def __setitem__(self, indices: Tuple[int, int], value: str) -> None: ... @overload def __setitem__(self, indices: Tuple[int, slice], value: Iterable[str]) -> None: ... @overload def __setitem__(self, indices: Tuple[slice, int], value: Iterable[str]) -> None: ... @overload def __setitem__(self, indices: Tuple[slice, slice], value: Iterable[Iterable[str]]) -> None: ... def __setitem__(self, indices, value): """Set table data using indices or slices. The shape of the **value** argument must match the provided indices/slices.""" row_idx, col_idx = indices ## both row_idx and col_idx are slices. value is an iterable of iterables if isinstance(row_idx, slice) and isinstance(col_idx, slice): if row_idx == slice(None) and col_idx == slice(None): data = value # overwrite entire table data else: data = self._get_data( ) # overwrite just sliced rows and columns for data_row, set_row in zip(data[row_idx], value): data_row[col_idx] = set_row dpgcore.set_table_data(self.id, data) ## just row_idx is a slice. value is an iterable elif isinstance(row_idx, slice): data = self._get_data() for row, s in zip(data[row_idx], value): row[col_idx] = s dpgcore.set_table_data(self.id, data) ## just col_idx is a slice. value is an iterable elif isinstance(col_idx, slice): data = self._get_data() data[row_idx][col_idx] = value dpgcore.set_table_data(self.id, data) ## neither are slices else: dpgcore.set_table_item(self.id, row_idx, col_idx, value) def clear(self) -> None: """Clear the table. This will remove all rows from the table. It does NOT change the table headers and therefore the number of visible columns.""" dpgcore.clear_table(self.id) def append_row(self, row: Iterable[str]) -> None: dpgcore.add_row(self.id, list(row)) def insert_row(self, row_idx: int, row: Iterable[str]) -> None: dpgcore.insert_row(self.id, row_idx, list(row)) def remove_row(self, row_idx: int) -> None: dpgcore.delete_row(self.id, row_idx) def append_column(self, header: str, column: Iterable[str]) -> None: dpgcore.add_column(self.id, header, list(column)) def insert_column(self, col_idx: int, header: str, column: Iterable[str]) -> None: dpgcore.insert_column(self.id, col_idx, header, list(column)) def remove_column(self, col_idx: int) -> None: dpgcore.delete_column(self.id, col_idx)
class Plot(Widget, ItemWidget): """A rich plot widget.""" ## Plot Axes xaxis: PlotXAxisConfig = PlotXAxis() #: The X-axis yaxis: PlotYAxisConfig = PlotYAxis(0) #: The Y-axis y2axis: PlotOptYAxisConfig = PlotYAxis(1, 'yaxis2') #: Optional Y-axis 2 y3axis: PlotOptYAxisConfig = PlotYAxis(2, 'yaxis3') #: Optional Y-axis 3 ## Config Properties label: str = ConfigProperty() x_axis_label: str = ConfigProperty(key='x_axis_name') y_axis_label: str = ConfigProperty(key='y_axis_name') show_annotations: bool = ConfigProperty() show_drag_lines: bool = ConfigProperty() show_drag_points: bool = ConfigProperty() show_color_scale: bool = ConfigProperty() scale_min: float = ConfigProperty() scale_max: float = ConfigProperty() scale_height: int = ConfigProperty() equal_aspects: bool = ConfigProperty() query: bool = ConfigProperty() crosshairs: bool = ConfigProperty() no_legend: bool = ConfigProperty() no_menus: bool = ConfigProperty() no_box_select: bool = ConfigProperty() no_mouse_pos: bool = ConfigProperty() no_highlight: bool = ConfigProperty() no_child: bool = ConfigProperty() anti_aliased: bool = ConfigProperty() def __init__(self, *, name_id: str = None, **config): # not super happy that we have to resort to typing.cast() here, but it works self._xaxis_config = PlotXAxisConfig(self, cast(PlotXAxis, Plot.xaxis)) self._yaxis_config = PlotYAxisConfig(self, cast(PlotYAxis, Plot.yaxis)) self._y2axis_config = PlotOptYAxisConfig(self, cast(PlotYAxis, Plot.y2axis)) self._y3axis_config = PlotOptYAxisConfig(self, cast(PlotYAxis, Plot.y3axis)) super().__init__(name_id=name_id, **config) def _setup_add_widget(self, dpg_args) -> None: dpgcore.add_plot(self.id, **dpg_args) def add_dataseries(self, series: DataSeries, *, update_bounds: bool = True) -> None: """Add a :class:`.DataSeries` to this plot (or update it). Updates the data series if it has already been added.""" series.update_plot(self, update_bounds) def remove_dataseries(self, series: DataSeries) -> None: """Remove a :class:`.DataSeries` from this plot if it has been added.""" dpgcore.delete_series(self.id, series.id) def clear(self) -> None: dpgcore.clear_plot(self.id) def set_xlimits(self, limits: Optional[PlotLimits]) -> None: """Set the ``(min, max)`` limits for the x-axis, or pass ``None`` to use automatic limits.""" if limits is None: dpgcore.set_plot_xlimits_auto(self.id) else: dpgcore.set_plot_xlimits(self.id, *limits) def set_ylimits(self, limits: Optional[PlotLimits]) -> None: """Set the ``(min, max)`` limits for the y-axis, or pass ``None`` to use automatic limits.""" if limits is None: dpgcore.set_plot_ylimits_auto(self.id) else: dpgcore.set_plot_ylimits(self.id, *limits) def set_xticks(self, ticks: Optional[Iterable[TickLabel]]) -> None: """Set the tick labels for the x-axis, or pass ``None`` to use automatic ticks.""" if ticks is None: dpgcore.reset_xticks(self.id) else: dpgcore.set_xticks(self.id, ticks) def set_yticks(self, ticks: Optional[Iterable[TickLabel]]) -> None: """Set the tick labels for the y-axis, or pass ``None`` to use automatic ticks.""" if ticks is None: dpgcore.reset_yticks(self.id) else: dpgcore.set_yticks(self.id, ticks)
class InputText(Widget, ItemWidgetMx, ValueWidgetMx[str]): """A text input box.""" value: str #: The inputted text. hint: str = ConfigProperty() multiline: bool = ConfigProperty() no_spaces: bool = ConfigProperty() uppercase: bool = ConfigProperty() tab_input: bool = ConfigProperty() decimal: bool = ConfigProperty() hexadecimal: bool = ConfigProperty() readonly: bool = ConfigProperty() password: bool = ConfigProperty() scientific: bool = ConfigProperty() label: str = ConfigProperty() on_enter: bool = ConfigProperty() def __init__(self, label: str = None, value: str = '', **config): super().__init__(label=label, default_value=value, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_input_text(self.id, **dpg_args)