Last active
February 2, 2021 15:40
-
-
Save kmatch98/3a222a989349abb50f0f8b92a8f1cf1d to your computer and use it in GitHub Desktop.
Second draft of a sliding switch widget (SwitchRoundHorizontal). Now updated with Control, Widget and WidgetLabel Classes. Includes an example to run on the PyPortal.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is a trial of the switch_round_horizontal | |
# for use on the PyPortal | |
# | |
# To do: | |
# Allow color handling for both RGB tuples (255, 255, 255) or hex values (0xFFFFFF) | |
# | |
import time | |
import board | |
import displayio | |
from adafruit_display_shapes.rect import Rect | |
from switch_round_horizontal import SwitchRoundHorizontal as Switch | |
import adafruit_touchscreen | |
from adafruit_pyportal import PyPortal | |
display = board.DISPLAY | |
screen_width = 320 | |
screen_height = 240 | |
ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR, | |
board.TOUCH_YD, board.TOUCH_YU, | |
calibration=((5200, 59000), | |
(5800, 57000)), | |
size=(screen_width, screen_height)) | |
switch_x = 30 | |
switch_y = 30 | |
switch_radius = 20 | |
switch_fill_color_off = (66, 44, 66) | |
switch_fill_color_on = (0, 100, 0) | |
switch_outline_color_off = (30, 30, 30) | |
switch_outline_color_on = (0, 60, 0) | |
background_color_off = (255, 255, 255) | |
background_color_on = (90, 255, 90) | |
background_outline_color_off = background_color_off | |
background_outline_color_on = background_color_on | |
switch_width=4*switch_radius # This is a good aspect ratio to start with | |
switch_stroke = 2 # Width of the outlines (in pixels) | |
text_stroke = switch_stroke # width of text lines | |
touch_padding = 0 # Additional boundary around widget that will accept touch input | |
animation_time = 0.2 # time for switch to display change (in seconds). 0.15 is a good starting point | |
display_text = True # show the text (0/1) | |
# initialize state variables | |
switch_value=False | |
switch_value=True | |
my_switch=Switch(x=switch_x, y=switch_y, | |
height=switch_radius*2, | |
fill_color_off=switch_fill_color_off, | |
fill_color_on=switch_fill_color_on, | |
outline_color_off=switch_outline_color_off, | |
outline_color_on=switch_outline_color_on, | |
background_color_off=background_color_off, | |
background_color_on=background_color_on, | |
background_outline_color_off=background_outline_color_off, | |
background_outline_color_on=background_outline_color_on, | |
switch_stroke=switch_stroke, | |
display_button_text=display_text, | |
touch_padding=10, | |
animation_time=animation_time, | |
value=False) | |
my_switch2=Switch(x=switch_x+100, y=switch_y, | |
height=switch_radius*2, | |
fill_color_off=switch_fill_color_off, | |
fill_color_on=switch_fill_color_on, | |
outline_color_off=switch_outline_color_off, | |
outline_color_on=switch_outline_color_on, | |
background_color_off=background_color_off, | |
background_color_on=background_color_on, | |
background_outline_color_off=background_outline_color_off, | |
background_outline_color_on=background_outline_color_on, | |
switch_stroke=switch_stroke, | |
display_button_text=False, | |
touch_padding=touch_padding, | |
animation_time=animation_time, | |
value=False) | |
my_switch3=Switch(x=switch_x, y=switch_y+55, | |
height=switch_radius*2, | |
fill_color_off=(255, 0, 0), | |
fill_color_on=switch_fill_color_on, | |
outline_color_off=(80, 0, 0), | |
outline_color_on=switch_outline_color_on, | |
background_color_off=(150, 0, 0), | |
background_color_on=background_color_on, | |
background_outline_color_off=(30, 0, 0), | |
background_outline_color_on=background_outline_color_on, | |
switch_stroke=switch_stroke, | |
display_button_text=True, | |
touch_padding=touch_padding, | |
animation_time=animation_time, | |
value=False) | |
my_switch4=Switch(x=switch_x+100, y=switch_y+55, | |
height=switch_radius*2, | |
fill_color_off=(255, 0, 0), | |
fill_color_on=switch_fill_color_on, | |
outline_color_off=(80, 0, 0), | |
outline_color_on=switch_outline_color_on, | |
background_color_off=(150, 0, 0), | |
background_color_on=background_color_on, | |
background_outline_color_off=(30, 0, 0), | |
background_outline_color_on=background_outline_color_on, | |
switch_stroke=switch_stroke, | |
display_button_text=False, | |
touch_padding=touch_padding, | |
animation_time=animation_time, | |
value=False) | |
my_switch5=Switch(x=0, y=0, | |
name="BIG Switch", | |
height=switch_radius*4, | |
fill_color_off=switch_fill_color_off, | |
fill_color_on=switch_fill_color_on, | |
outline_color_off=switch_outline_color_off, | |
outline_color_on=switch_outline_color_on, | |
background_color_off=background_color_off, | |
background_color_on=background_color_on, | |
background_outline_color_off=background_outline_color_off, | |
background_outline_color_on=background_outline_color_on, | |
switch_stroke=switch_stroke, | |
display_button_text=True, | |
touch_padding=10, | |
animation_time=0.3, # for larger button, may want to extend the animation time | |
text_stroke=6, ## Add a wider text stroke | |
value=False) | |
my_switch5.anchor_point=(1,1) | |
my_switch5.anchored_position=(305, 225) | |
my_group=displayio.Group(max_size=8) | |
my_group.append(my_switch) | |
my_group.append(my_switch2) | |
my_group.append(my_switch3) | |
my_group.append(my_switch4) | |
my_group.append(my_switch5) | |
# Add my_group to the display | |
display.show(my_group) | |
display.refresh(target_frames_per_second=60, minimum_frames_per_second=30) | |
# Start the main loop | |
while True: | |
p = ts.touch_point | |
#print("touch_point p: {}".format(p)) | |
if p: | |
#this_switch=my_switch5 | |
#print("this_switch x,y: {},{}, touch_boundary: {}".format(this_switch.x, this_switch.y, this_switch.touch_boundary)) | |
#print("bounding_box: {}".format(this_switch.bounding_box)) | |
if my_switch.contains(p): | |
my_switch.selected(p) | |
elif my_switch2.contains(p): | |
my_switch2.selected(p) | |
elif my_switch3.contains(p): | |
my_switch3.selected(p) | |
elif my_switch4.contains(p): | |
my_switch4.selected(p) | |
elif my_switch5.contains(p): | |
#print("switch5 touched") | |
my_switch5.selected(p) | |
time.sleep(0.05) # touch responce is more accurate with a small delayed added |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The MIT License (MIT) | |
# | |
# Copyright (c) 2021 Kevin Matocha (kmatch98) | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# | |
# | |
# CircuitPython GUI Control Class for touch-related elements | |
# | |
# Defines the key response functions for touch controls: | |
# - contains: evaluates if touch_point is within the Control's self.touch_boundary | |
# - selected | |
# - still_touched | |
# - released | |
# - gesture_response | |
class Control: | |
"""A Control class for responsive elements, including several response functions, | |
including for touch response.""" | |
def __init__( | |
self, | |
): | |
self.touch_boundary=None # should be overridden by subclass | |
def contains(self, touch_point): | |
"""Checks if the Control was touched. Returns True if the touch_point | |
is within the Control's touch_boundary.""" | |
# The touch_point should be in local coordinates for this item. | |
if ((self.touch_boundary is not None) and | |
((self.touch_boundary[0] <= touch_point[0] <= (self.touch_boundary[0]+self.touch_boundary[2])) and | |
(self.touch_boundary[1] <= touch_point[1] <= (self.touch_boundary[1]+self.touch_boundary[3])) ) | |
): | |
return True | |
return False | |
# place holder touch_handler response functions | |
def selected(self, touch_point): | |
"""Response function when Control is selected.""" | |
pass | |
def still_touched(self, touch_point): # *** this needs a clearer name | |
"""Response function when Control remains touched.""" | |
pass | |
def released(self, touch_point): | |
"""Response function when Control is released.""" | |
pass | |
def gesture_response(self, gesture): | |
"""Response function to handle gestures (future expansion).""" | |
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The MIT License (MIT) | |
# | |
# Copyright (c) 2021 Kevin Matocha (kmatch98) | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# | |
################################ | |
# A round switch widget for CircuitPython, using displayio and adafruit_display_shapes | |
# | |
# Features: | |
# - Color grading as the switch animates between the off and on states | |
# - Option to display 0 or 1 to confirm the switch state (display_button_text=True) | |
# - Provides setting for animation_time (approximate), and adapts redraw rate based on real time. | |
# | |
# Future options to consider: | |
# --------------------------- | |
# different orientations (horizontal, vertical, flipped) | |
# | |
import time | |
import terminalio | |
from widget import Widget | |
from control import Control | |
from widget_label import WidgetLabel | |
from adafruit_display_shapes.circle import Circle | |
from adafruit_display_shapes.roundrect import RoundRect | |
from adafruit_display_shapes.rect import Rect | |
class SwitchRoundHorizontal(Widget, Control): | |
"""A horizontal sliding switch widget. The origin is set using ``x`` and ``y``. | |
:param int x: pixel position | |
:param int y: pixel position | |
:param int width: width of the switch in pixels, set to ``None`` to auto-size | |
relative to the height | |
:param int height: height of the switch in pixels | |
:param str name: name of the switch | |
:param float anchor_point: (X,Y) values from 0.0 to 1.0 to define the anchor | |
point relative to the switch bounding box | |
:param int anchored_position: (x,y) pixel value for the location | |
of the anchor_point | |
:param fill_color_off: switch off-state fill color (RGB tuple | |
or 24-bit hex value) | |
:param fill_color_on: switch on-state fill color (RGB tuple or | |
24-bit hex value) | |
:param outline_color_off: switch off-state outline color (RGB | |
tuple or 24-bit hex value) | |
:param outline_color_on: switch on-state outline color (RGB tuple | |
or 24-bit hex value) | |
:param background_color_off: background off-state color (RGB tuple | |
or 24-bit hex value) | |
:param background_color_on: background on-state color (RGB tuple | |
or 24-bit hex value) | |
:param background_outline_color_off: background outline off-state | |
color (RGB tuple or 24-bit hex value) | |
:param background_outline_color_on: background outline on-state | |
color (RGB tuple or 24-bit hex value) | |
:param int switch_stroke: outline stroke width for the switch, in pixels | |
:param int text_stroke: outline stroke width for the 0/1 text, in pixels | |
:param Boolean display_button_text: Set True to display the 0/1 text | |
on the sliding switch | |
:param float animation_time: time for the switching animation, in seconds | |
a value of 0.2 is a good starting point""" | |
# This Switch has multiple class inheritances. | |
# It is a subclass of Group->Widget and a sublcass of Control. | |
def __init__( | |
self, | |
value=False, | |
touch_padding=0, | |
anchor_point=None, | |
anchored_position=None, | |
fill_color_off=(66, 44, 66), | |
fill_color_on=(0,100,0), | |
outline_color_off=(30,30,30), | |
outline_color_on=(0,60,0), | |
background_color_off=(255,255,255), | |
background_color_on=(0,60,0), | |
background_outline_color_off=None, # default to background_color_off | |
background_outline_color_on=None, # default to background_color_on | |
switch_stroke=2, | |
text_stroke=None, # default to switch_stroke | |
display_button_text=True, | |
animation_time=0.2, # animation duration (in seconds) | |
**kwargs, | |
): | |
#initialize the Widget superclass (x, y, scale) | |
super().__init__(**kwargs, max_size=4) | |
# Define how many graphical elements will be in this group | |
# using "max_size=XX" | |
# | |
# Group elements for SwitchRoundHorizontal: | |
# 1. switch_roundrect: The switch background | |
# 2. switch_circle: The switch button | |
# 3. Optional - widget label | |
# 4. Optional - text_0 or text_1: The 0/1 text on the switch button | |
# initialize the Control superclass | |
super(Control, self).__init__() | |
self._radius = self.height//2 | |
switch_x = self._radius | |
switch_y = self._radius | |
if self._width is None: | |
self._width = 4 * self._radius | |
else: | |
self._width = self._width | |
if background_outline_color_off is None: | |
background_outline_color_off = background_color_off | |
if background_outline_color_on is None: | |
background_outline_color_on = background_color_on | |
self._fill_color_off = fill_color_off | |
self._fill_color_on = fill_color_on | |
self._outline_color_off = outline_color_off | |
self._outline_color_on = outline_color_on | |
self._background_color_off = background_color_off | |
self._background_color_on = background_color_on | |
self._background_outline_color_off = background_outline_color_off | |
self._background_outline_color_on = background_outline_color_on | |
self._switch_stroke=switch_stroke | |
if text_stroke is None: | |
text_stroke = switch_stroke # width of text lines | |
self._text_stroke = text_stroke | |
self._display_button_text = display_button_text # state variable whether text (0/1) is displayed | |
self._touch_padding = touch_padding | |
self._animation_time = animation_time | |
self._value = value | |
self._text_0_on = not value # controls which text value is displayed (0 or 1) | |
self._anchor_point = anchor_point | |
self._anchored_position = anchored_position | |
# initialize the display elements | |
self._switch_circle = Circle(x0=switch_x, y0=switch_y, | |
r=self._radius, | |
fill=self._fill_color_off, | |
outline=self._outline_color_off, | |
stroke=self._switch_stroke) | |
self._switch_roundrect = RoundRect( x=switch_x-self._radius, | |
y=switch_y-self._radius, | |
r=self._radius, | |
width=self._width, height=2*self._radius+1, | |
fill=self._background_color_off, | |
outline=self._background_outline_color_off, | |
stroke=self._switch_stroke) | |
# bounding_box defines the "local" x and y. | |
# Must be offset by self.x and self.y to get the raw display coordinates | |
self._bounding_box = [self._switch_circle.x, | |
self._switch_circle.y, | |
self._width, | |
2*self._radius+1] | |
self.touch_boundary = [self._bounding_box[0]-self._touch_padding, | |
self._bounding_box[1]-self._touch_padding, | |
self._bounding_box[2]+2*self._touch_padding, | |
self._bounding_box[3]+2*self._touch_padding] | |
# The "0" text circle | |
self._text_0 = Circle(x0=switch_x, y0=switch_y, | |
r=self._radius//2, | |
fill=self._fill_color_off, | |
outline=self._outline_color_off, | |
stroke=self._text_stroke) | |
# The "1" text rectangle | |
self._text_1 = Rect(x=switch_x-self._switch_stroke+1, y=switch_y-self._radius//2, | |
height=self._radius, | |
width=self._text_stroke, | |
fill=self._fill_color_off, | |
outline=self._outline_color_off, | |
stroke=self._text_stroke) | |
# Store initial positions of the moving parts | |
self._switch_initial_x=self._switch_circle.x | |
self._text_0_initial_x=self._text_0.x | |
self._text_1_initial_x=self._text_1.x | |
# Set the initial switch position based on the starting value | |
if value: | |
self._draw_position(1) | |
else: | |
self._draw_position(0) | |
# Add the display elements to the self group | |
self.append(self._switch_roundrect) | |
self.append(self._switch_circle) | |
# Create the widget label | |
self.widget_label=None | |
if (self.name != "") : | |
font=terminalio.FONT | |
self.widget_label=WidgetLabel(font, | |
self, | |
anchor_point=(1,0.5), | |
anchor_point_on_widget=(-0.05, 0.5)) | |
# If display_button_text is True, append the correct text element (0 or 1) | |
if display_button_text: | |
if (self._text_0_on): | |
self.append(self._text_0) | |
else: | |
self.append(self._text_1) | |
# update the position, if required | |
self._update_position | |
def _draw_position(self, position): | |
# Draw the position of the slider. | |
# The position is a float between 0 and 1 (0= off, 1= on). | |
# To deal with any rounding errors | |
if position <= 0: # left-end position | |
self._switch_circle.x = self._switch_initial_x | |
self._text_0.x=self._text_0_initial_x | |
self._text_1.x=self._text_1_initial_x | |
elif position >= 1: # right-end position | |
self._switch_circle.x = self._switch_initial_x + self._width-2*self._radius-1 | |
self._text_0.x = self._text_0_initial_x + self._width-2*self._radius-1 | |
self._text_1.x = self._text_1_initial_x + self._width-2*self._radius-1 | |
else: # somewhere in the middle | |
self._switch_circle.x = self._switch_initial_x + int((self._width - (2 * self._radius) - 1)*position) | |
self._text_0.x = self._text_0_initial_x + int((self._width - (2 * self._radius) - 1)*position) | |
self._text_1.x = self._text_1_initial_x + int((self._width - (2 * self._radius) - 1)*position) | |
# Set the color to the correct fade | |
self._switch_circle.fill = _color_fade(self._fill_color_off, self._fill_color_on, position) | |
self._switch_circle.outline = _color_fade(self._outline_color_off, self._outline_color_on, position) | |
self._switch_roundrect.fill = _color_fade(self._background_color_off, self._background_color_on, position) | |
self._switch_roundrect.outline = _color_fade(self._background_outline_color_off, self._background_outline_color_on, position) | |
self._text_0.fill = self._switch_circle.fill | |
self._text_1.fill = self._switch_circle.fill | |
self._text_0.outline = self._switch_circle.outline | |
self._text_1.outline = self._switch_circle.outline | |
if (self._display_button_text and position > 0.5 and self._text_0_on): | |
self.pop() | |
self.append(self._text_1) | |
self._text_0_on = False | |
elif (self._display_button_text and position < 0.5 and not self._text_0_on): | |
self.pop() | |
self.append(self._text_0) | |
self._text_0_on = True | |
def selected(self, touch_point): | |
# requires passing display to allow auto_refresh off when redrawing | |
# touch_point is adjusted for group's x,y position before sending to super() | |
start_time=time.monotonic() | |
while True: | |
if self._value: | |
position = 1 - (time.monotonic() - start_time)/self._animation_time # fraction from 0 to 1 | |
else: | |
position = (time.monotonic() - start_time)/self._animation_time # fraction from 0 to 1 | |
self._draw_position(position) # update the switch position | |
if ((position >= 1) and not self._value): # ensures that the final position is drawn | |
self._value=True | |
break | |
if ((position <= 0) and self._value): # ensures that the final position is drawn | |
self._value=False | |
break | |
touch_x = touch_point[0] - self.x # adjust touch position for the local position | |
touch_y = touch_point[1] - self.y | |
super().selected((touch_x, touch_y, 0)) | |
def contains(self, touch_point): # overrides, then calls Control.contains(x,y) | |
"""Returns True if the touch_point is within the widget's touch_boundary.""" | |
touch_x = touch_point[0] - self.x # adjust touch position for the local position | |
touch_y = touch_point[1] - self.y | |
return super().contains((touch_x, touch_y, 0)) | |
@property | |
def value(self): | |
"""The current switch value (Boolean).""" | |
return self._value | |
@value.setter | |
def value(self, new_value): | |
if (new_value != self._value): | |
fake_touch_point=[0,0,0] # send an arbitrary touch_point | |
self.selected(fake_touch_point) | |
###### color support functions ###### | |
def _color_to_tuple(value): | |
"""Converts a color from a 24-bit integer to a tuple. | |
:param value: RGB LED desired value - can be a RGB tuple or a 24-bit integer. | |
""" | |
if isinstance(value, tuple): | |
return value | |
elif isinstance(value, int): | |
if value >> 24: | |
raise ValueError("Only bits 0->23 valid for integer input") | |
r = value >> 16 | |
g = (value >> 8) & 0xFF | |
b = value & 0xFF | |
return [r, g, b] | |
else: | |
raise ValueError("Color must be a tuple or 24-bit integer value.") | |
def _color_fade(start_color, end_color, fraction): | |
"""Linear extrapolation of a color between two RGB colors (tuple or 24-bit integer). | |
: param start_color: starting color | |
: param end_color: ending color | |
: param fraction: Floating point number ranging from 0 to 1 indicating what | |
fraction of interpolation between start_color and end_color. | |
""" | |
start_color =_color_to_tuple(start_color) | |
end_color = _color_to_tuple(end_color) | |
if fraction >= 1: | |
return end_color | |
if fraction <= 0: | |
return start_color | |
else: | |
faded_color = [0,0,0] | |
for i in range(3): | |
faded_color[i] = start_color[i] - int((start_color[i]-end_color[i])* fraction) | |
return faded_color | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The MIT License (MIT) | |
# | |
# Copyright (c) 2021 Kevin Matocha (kmatch98) | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# | |
# | |
# CircuitPython GUI Widget Class for visual elements | |
# | |
# Properties: | |
# - width | |
# - height | |
# - name | |
# - anchor_point | |
# - anchored_position | |
import displayio | |
class Widget(displayio.Group): | |
"""A Widget class definition for graphical display elements. | |
:param int x: pixel position | |
:param int y: pixel position | |
:param int width: width of the switch in pixels, set to ``None`` to auto-size | |
relative to the height | |
:param int height: height of the switch in pixels | |
:param str name: name of the switch | |
:param float anchor_point: (X,Y) values from 0.0 to 1.0 to define the anchor | |
point relative to the switch bounding box | |
:param int anchored_position: (x,y) pixel value for the location | |
of the anchor_point""" | |
def __init__( | |
self, | |
width=None, | |
height=None, | |
name="", | |
anchor_point=None, | |
anchored_position=None, | |
bounding_box=None, # pixel extent of the widget [x0, y0, width, height] | |
**kwargs, | |
): | |
super().__init__(**kwargs) # should send x,y and scale (optional) to Group | |
self._width = width | |
self._height = height | |
self.name = name | |
self._anchor_point = anchor_point | |
self._anchored_position = anchored_position | |
# self.bounding_box: pixel extent of the widget [x0, y0, width, height] | |
if bounding_box is None: | |
if ( (width is not None) and | |
(height is not None) ): | |
self._bounding_box = [0, 0, width, height] | |
else: | |
self._bounding_box = [0, 0, 0, 0] | |
self._update_position | |
def _update_position(self): | |
# Reposition self.x, self.y based on anchor_point and anchored_position | |
if (self._anchor_point is not None) and (self._anchored_position is not None): | |
self.x=self._anchored_position[0]-int(self._anchor_point[0]*self._bounding_box[2]) - self._bounding_box[0] | |
self.y=self._anchored_position[1]-int(self._anchor_point[1]*self._bounding_box[3]) - self._bounding_box[1] | |
@property | |
def anchor_point(self): | |
"""The anchor point for positioning the switch, works in concert with `anchored_position`.""" | |
return self._anchor_point | |
@anchor_point.setter | |
def anchor_point(self, new_anchor_point): | |
self._anchor_point = new_anchor_point | |
self._update_position() | |
@property | |
def anchored_position(self): | |
"""The anchored position for positioning the switch, works in concert with `anchor_point`.""" | |
return self._anchored_position | |
@anchored_position.setter | |
def anchored_position(self, new_anchored_position): | |
self._anchored_position = new_anchored_position | |
self._update_position() | |
@property | |
def bounding_box(self): | |
"""The boundary of the widget. [x, y, width, height] in widget coordinates.""" | |
return self._bounding_box | |
@property | |
def width(self): | |
"""The widget width, in pixels. Must be defined at instance.""" | |
return self._width | |
@property | |
def height(self): | |
"""The widget height, in pixels. Must be defined at instance.""" | |
return self._height | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The MIT License (MIT) | |
# | |
# Copyright (c) 2021 Kevin Matocha (kmatch98) | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# | |
# | |
# CircuitPython GUI Widget Class for visual elements | |
# | |
# Properties: | |
# - name | |
# - font | |
# - anchor_point | |
# - anchor_point_on_widget | |
import displayio | |
from adafruit_display_text import bitmap_label | |
class WidgetLabel(bitmap_label.Label): | |
"""A WidgetLabel class to connect a label to a widget. | |
The ``anchor_point`` and ``anchor_point_on_widget`` along with | |
the widget's ``bounding_box`` are used to position the label.""" | |
def __init__( | |
self, | |
font, | |
Widget, | |
anchor_point_on_widget=None, | |
**kwargs, | |
): | |
super().__init__(font, text=Widget.name, **kwargs) | |
self.anchor_point_on_widget = anchor_point_on_widget | |
self.update_label_position(Widget.bounding_box) | |
Widget.append(self) | |
# Widget label position is adjusted so that the ``anchor_point`` on the label | |
# is set to the ``anchor_point_on_widget`` location. This function requires | |
# the widget's ``bounding_box`` as a parameter. | |
def update_label_position(self, bounding_box): | |
if ( (bounding_box is None) or | |
(self.anchor_point is None) or | |
(self.anchor_point_on_widget) is None ): | |
pass | |
else: | |
x0 = bounding_box[0] | |
y0 = bounding_box[1] | |
width = bounding_box[2] | |
height = bounding_box[3] | |
anchored_position_x = x0 + int(width * self.anchor_point_on_widget[0]) | |
anchored_position_y = y0 + int(height * self.anchor_point_on_widget[1]) | |
self.anchored_position=(anchored_position_x, anchored_position_y) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment