Skip to content

Instantly share code, notes, and snippets.

@raitonoberu
Created May 16, 2021 10:03
Show Gist options
  • Save raitonoberu/af76d9b5813b7879e8db940bafa0f325 to your computer and use it in GitHub Desktop.
Save raitonoberu/af76d9b5813b7879e8db940bafa0f325 to your computer and use it in GitHub Desktop.
Clone of the Fluent UI NavigationPane indicator (pygame)
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