Instantly share code, notes, and snippets.
Created
May 16, 2021 10:03
-
Star
(1)
1
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save raitonoberu/af76d9b5813b7879e8db940bafa0f325 to your computer and use it in GitHub Desktop.
Clone of the Fluent UI NavigationPane indicator (pygame)
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
import pygame | |
import pygame.draw | |
import pygame.locals | |
from math import sqrt | |
kOneLineTileHeight = 40 | |
kNavigationIndicatorHeight = 24 | |
kNavigationIndicatorWidth = 2 | |
kNavigationIndicatorGap = 4 | |
class PaneItem(object): | |
rect = None | |
hover = False | |
def draw(self, surface): | |
if self.hover: | |
color = pygame.Color(90, 90, 90) | |
else: | |
color = pygame.Color(30, 30, 30) | |
pygame.draw.rect(surface, color, self.rect) | |
class NavigationIndicator(object): | |
def __init__(self, x, y, index) -> None: | |
self.x = x | |
self.y = y | |
self.oldIndex = index | |
self.newIndex = index | |
self.titleHeight = kOneLineTileHeight | |
self.height = kNavigationIndicatorHeight | |
self.width = kNavigationIndicatorWidth | |
self.gap = kNavigationIndicatorGap | |
self.step = 0.5 | |
self.startDelay = 8 # delay between 2 dots (in frames) | |
self.p1Start = 0.0 | |
self.p2Start = 0.0 | |
self.p1End = 0.0 | |
self.p2End = 0.0 | |
self.p1 = 0 # percentage of 1st point (0..1) | |
self.p2 = 0 # percentage of 2nd point (0..1) | |
self.delay = 0 | |
def calcVelocity(self, p): | |
if p < 0.5: | |
return (1 - sqrt(1 - pow(2 * p, 2))) / 2 | |
return (sqrt(1 - pow(-2 * p + 2, 2)) + 1) / 2 | |
def update(self, index): | |
if index != self.newIndex: | |
self.oldIndex = self.newIndex | |
self.newIndex = index | |
self.p1 = 0 | |
self.p2 = 0 | |
self.delay = self.startDelay | |
minIndex = self.oldIndex | |
maxIndex = self.newIndex | |
self.p1Start = ( | |
self.y | |
+ self.titleHeight * minIndex | |
+ (self.titleHeight / 2 - self.height / 2) | |
) | |
self.p1End = ( | |
self.y | |
+ self.titleHeight * maxIndex | |
+ (self.titleHeight / 2 - self.height / 2) | |
) | |
self.p2Start = ( | |
self.y + self.titleHeight * minIndex + (self.titleHeight - self.height / 2) | |
) | |
self.p2End = ( | |
self.y + self.titleHeight * maxIndex + (self.titleHeight - self.height / 2) | |
) | |
if self.p2Start > self.p2End: | |
# move up | |
v1 = self.calcVelocity(self.p1) + 0.1 | |
self.p1 = min(self.p1 + self.step * v1, 1) | |
if self.delay == 0: | |
v2 = self.calcVelocity(self.p2) + 0.1 | |
self.p2 = min(self.p2 + self.step * v2, 1) | |
else: | |
# move down | |
v2 = self.calcVelocity(self.p2) + 0.1 | |
self.p2 = min(self.p2 + self.step * v2, 1) | |
if self.delay == 0: | |
v1 = self.calcVelocity(self.p1) + 0.1 | |
self.p1 = min(self.p1 + self.step * v1, 1) | |
if self.delay > 0: | |
self.delay -= 1 | |
def draw(self, surface): | |
pygame.draw.line( | |
surface, | |
pygame.Color(60, 78, 110), | |
(self.x + self.gap, self.p1Start + (self.p1End - self.p1Start) * self.p1), | |
(self.x + self.gap, self.p2Start + (self.p2End - self.p2Start) * self.p2), | |
self.width, | |
) | |
class NavigationPane(object): | |
def __init__( | |
self, | |
x, | |
y, | |
w, | |
items=[], | |
selected=None, | |
onChanged=lambda _: None, | |
): | |
self.x = x | |
self.y = y | |
self.w = w | |
self.items = items | |
self.selected = selected | |
self.onChanged = onChanged | |
self.navigationIndicator = NavigationIndicator(x, y, selected) | |
# give all items a size | |
y = 0 | |
for item in self.items: | |
item.rect = pygame.Rect(self.x, self.y + y, self.w, kOneLineTileHeight) | |
y += kOneLineTileHeight | |
def update(self): | |
self.navigationIndicator.update(self.selected) | |
for item in self.items: | |
if item.rect.collidepoint(pygame.mouse.get_pos()): | |
# detect hover & click | |
item.hover = True | |
if pygame.mouse.get_pressed()[0]: | |
index = self.items.index(item) | |
if index != self.selected: | |
self.onChanged(index) | |
else: | |
item.hover = False | |
def draw(self, surface): | |
for item in self.items: | |
item.draw(surface) | |
self.navigationIndicator.draw(surface) | |
if __name__ == "__main__": | |
pygame.init() | |
clock = pygame.time.Clock() | |
display = pygame.display.set_mode((200, 300)) | |
nav = NavigationPane( | |
10, | |
10, | |
kOneLineTileHeight * 3, | |
items=[ | |
PaneItem(), | |
PaneItem(), | |
PaneItem(), | |
PaneItem(), | |
PaneItem(), | |
PaneItem(), | |
], | |
selected=0, | |
) | |
def onChanged(index): | |
# to pass it as callback | |
nav.selected = index | |
print(index, "selected") | |
nav.onChanged = onChanged | |
while True: | |
for event in pygame.event.get(): | |
if event.type == pygame.locals.QUIT: | |
pygame.quit() | |
display.fill((255, 255, 255)) | |
nav.update() | |
nav.draw(display) | |
pygame.display.update() | |
clock.tick(60) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment