Created
April 17, 2021 10:52
-
-
Save tshirtman/26bd680db5e847bab83387efaafd78f2 to your computer and use it in GitHub Desktop.
using a shader to build a bottom dock for buttons with a circle opening in it
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
''' | |
''' | |
from kivy.app import App | |
from kivy.lang import Builder | |
from kivy.uix.widget import Widget | |
from kivy.core.window import Window | |
from kivy.graphics import RenderContext | |
from kivy import properties as P | |
KV = ''' | |
#:import rgba kivy.utils.rgba | |
<CLabel@Label,CSlider@Slider>: | |
size_hint_y: None | |
height: '50dp' | |
<CColorPicker@ColorPicker> | |
size_hint_y: None | |
height: '400dp' | |
FloatLayout: | |
ScrollView: | |
GridLayout: | |
size_hint_y: None | |
height: self.minimum_height | |
cols: 2 | |
CLabel: | |
text: 'height' | |
CSlider: | |
id: height | |
min: 0 | |
max: 1000 | |
CLabel: | |
text: 'radius' | |
CSlider: | |
id: radius | |
min: 0 | |
max: 1000 | |
CLabel: | |
text: 'corner_radius' | |
CSlider: | |
id: corner_radius | |
min: 0 | |
max: 100 | |
CLabel: | |
text: 'ease' | |
CSlider: | |
id: ease | |
min: 0 | |
max: 10 | |
CLabel: | |
text: 'color' | |
CColorPicker: | |
id: color | |
color: rgba('#009688') | |
Dock: | |
radius: radius.value | |
corner_radius: corner_radius.value | |
color: color.color or (0, 0, 0, 0) | |
size_hint: 1, None | |
height: height.value | |
ease: ease.value | |
<Dock>: | |
canvas: | |
Rectangle: | |
pos: self.pos | |
size: self.size | |
''' | |
DOCK_SHADER = ''' | |
$HEADER$ | |
uniform vec2 resolution; | |
uniform float radius; | |
uniform float corner_radius; | |
uniform float ease; | |
uniform vec4 color; | |
// not available in glsl2, let's define it | |
float smooth_step(float edge0, float edge1, float x){ | |
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); | |
return t * t * (3.0 - 2.0 * t); | |
} | |
void main(void) { | |
vec4 frag_coord = frag_modelview_mat * gl_FragCoord; | |
float x = frag_coord.x; | |
float y = frag_coord.y; | |
vec2 circle_center = vec2(resolution.x / 2.0, resolution.y); | |
// smooth based on distance from circle center | |
float d = length(frag_coord.xy - circle_center); | |
float opacity = smooth_step(radius, radius + ease, d); | |
// smooth based on distance to top | |
opacity *= 1.0 - smooth_step(-ease, 0.0, frag_coord.y - resolution.y); | |
// round the corners | |
// left | |
vec2 left_corner = vec2( | |
circle_center.x - radius - corner_radius, | |
resolution.y - corner_radius | |
); | |
if ( | |
x > left_corner.x && x < circle_center.x - radius | |
&& y > left_corner.y && y < resolution.y | |
) { | |
opacity *= 1.0 - smooth_step( | |
corner_radius - ease, | |
corner_radius, | |
length(frag_coord.xy - left_corner) | |
); | |
} | |
// right | |
vec2 right_corner = vec2( | |
circle_center.x + radius + corner_radius, | |
resolution.y - corner_radius | |
); | |
if ( | |
x < right_corner.x && x > circle_center.x + radius | |
&& y > right_corner.y && y < resolution.y | |
) { | |
opacity *= 1.0 - smooth_step( | |
corner_radius - ease, | |
corner_radius, | |
length(frag_coord.xy - right_corner) | |
); | |
} | |
gl_FragColor = vec4(color.r, color.g, color.b, color.a * opacity); | |
} | |
''' | |
class Dock(Widget): | |
radius = P.NumericProperty('50dp') | |
color = P.ListProperty([1, 1, 1, 1]) | |
ease = P.NumericProperty(2) | |
corner_radius = P.NumericProperty('10dp') | |
def __init__(self, **kwargs): | |
self.canvas = RenderContext() | |
self.canvas.shader.fs = DOCK_SHADER | |
super().__init__(**kwargs) | |
self.bind( | |
size=self.update_glsl, | |
pos=self.update_glsl, | |
radius=self.update_glsl, | |
corner_radius=self.update_glsl, | |
color=self.update_glsl, | |
ease=self.update_glsl, | |
) | |
self.update_glsl() | |
def update_glsl(self, *largs): | |
self.canvas['resolution'] = list(map(float, self.size)) | |
self.canvas['radius'] = float(self.radius) | |
self.canvas['corner_radius'] = float(self.corner_radius) | |
self.canvas['color'] = list(map(float, self.color)) | |
self.canvas['ease'] = float(self.ease) | |
win_rc = Window.render_context | |
self.canvas['projection_mat'] = win_rc['projection_mat'] | |
self.canvas['modelview_mat'] = win_rc['modelview_mat'] | |
self.canvas['frag_modelview_mat'] = win_rc['frag_modelview_mat'] | |
class Application(App): | |
def build(self): | |
return Builder.load_string(KV) | |
if __name__ == "__main__": | |
Application().run() |
Author
tshirtman
commented
Apr 17, 2021
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment