Skip to content

Instantly share code, notes, and snippets.

@kmatch98
Last active February 2, 2021 15:40
Show Gist options
  • Save kmatch98/3a222a989349abb50f0f8b92a8f1cf1d to your computer and use it in GitHub Desktop.
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 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
# 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
# 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
# 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
# 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