Base implementation of drag and drop added
This commit is contained in:
parent
4265c306b9
commit
fff62bea5a
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from src.gui import *
|
||||
from src.dariuino import *
|
||||
|
||||
|
||||
class Argument:
|
||||
|
@ -21,16 +21,19 @@ class Statement:
|
|||
|
||||
|
||||
def main():
|
||||
resources = ResourceContainer.from_json_file('res/resources.json')
|
||||
resources = gui.ResourceContainer.from_json_file('res/resources.json')
|
||||
|
||||
container = Container(Position(0, 0), Dimensions(600, 600))
|
||||
container.set_background_image(resources.get_image('background'))
|
||||
container = gui.Container(gui.Position(0, 0), gui.Dimensions(600, 600))
|
||||
|
||||
v = Visualization(resources.get_image('sprite'), Position(100, 300))
|
||||
c = gui.Dragable(resources.get_image('icon-for-32'), gui.Position(100, 300))
|
||||
c.on_click = lambda : print('Hello world')
|
||||
|
||||
container.add_visualization(v)
|
||||
a = gui.Dragable(resources.get_image('icon-while-32'), gui.Position(150, 350))
|
||||
|
||||
ui = UI('Dariuino IDE', Dimensions(1920, 1080))
|
||||
container.add_clickable(c)
|
||||
container.add_clickable(a)
|
||||
|
||||
ui = gui.UI('Dariuino IDE', gui.Dimensions(1920, 1080))
|
||||
ui.add_container(container)
|
||||
ui.init()
|
||||
ui.update()
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 969 B |
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"images": {
|
||||
"background": "/home/mal/Bilder/65a671cb20fb3d0c5774aafc5e5971c4.jpg",
|
||||
"sprite": "/home/mal/Bilder/Icons/wow-moka.png"
|
||||
"icon-for-32": "res/graphics/icon-for-32.png",
|
||||
"icon-while-32": "res/graphics/icon-while-32.png"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import src.gui as gui
|
||||
|
||||
|
||||
class BlockColor:
|
||||
PINK = gui.Color(128, 0, 128)
|
||||
VIOLET = gui.Color(73, 0, 128)
|
||||
GREEN = gui.Color(0, 128, 0)
|
||||
BLUE = gui.Color(255, 0, 0)
|
||||
YELLOW = gui.Color(255, 255, 0)
|
||||
|
||||
|
||||
class CodeBlock(gui.Clickable):
|
||||
def __init__(self, color: gui.Color, on_click_callback: callable):
|
||||
Clickable.__init__(self, gui.Canvas(500, 200), gui.Position(0, 0))
|
||||
self.on_click = on_click_callable
|
||||
|
251
src/gui.py
251
src/gui.py
|
@ -1,7 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import math
|
||||
import pygame
|
||||
import json
|
||||
from typing import Self
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Position:
|
||||
|
@ -9,11 +10,31 @@ class Position:
|
|||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def as_tuple(self):
|
||||
def __add__(self, position: Self) -> Self:
|
||||
return Position(self.x + position.x, self.y + position.y)
|
||||
|
||||
def __sub__(self, position: Self) -> Self:
|
||||
return Position(self.x - position.x, self.y - position.y)
|
||||
|
||||
def __mul__(self, position: Self) -> Self:
|
||||
return Position(self.x * position.x, self.y * position.y)
|
||||
|
||||
def __str__(self):
|
||||
return 'Position(%d, %d)' % (self.x, self.y)
|
||||
|
||||
def as_tuple(self) -> Tuple:
|
||||
return (self.x, self.y)
|
||||
|
||||
def get_distance(self, position: Self) -> Self:
|
||||
delta = position - self
|
||||
|
||||
return math.sqrt(math.pow(delta.x, 2) + math.pow(delta.y, 2))
|
||||
|
||||
def get_duplicate(self) -> Self:
|
||||
return Position(self.x, self.y)
|
||||
|
||||
@staticmethod
|
||||
def from_tuple(tuple):
|
||||
def from_tuple(tuple) -> Self:
|
||||
return Position(tuple[0], tuple[1])
|
||||
|
||||
|
||||
|
@ -23,7 +44,7 @@ class Color:
|
|||
self.green = green
|
||||
self.blue = blue
|
||||
|
||||
def as_tuple(self):
|
||||
def as_tuple(self) -> Tuple:
|
||||
return (self.red, self.green, self.blue)
|
||||
|
||||
|
||||
|
@ -40,23 +61,12 @@ class Dimensions:
|
|||
return Dimensions(tuple[0], tuple[1])
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, path = None):
|
||||
self.surface = None
|
||||
class Drawable:
|
||||
def __init__(self, dimensions = Dimensions(0, 0)):
|
||||
self.surface = pygame.Surface(dimensions.as_tuple(), pygame.SRCALPHA)
|
||||
|
||||
if path is not None:
|
||||
self.surface = pygame.image.load(path)
|
||||
|
||||
def get_dimensions(self) -> Dimensions:
|
||||
return Dimensions.from_tuple(self.surface.get_size())
|
||||
|
||||
|
||||
class Canvas:
|
||||
def __init__(self, size: Dimensions):
|
||||
self.surface = pygame.Surface(size.as_tuple(), pygame.SRCALPHA)
|
||||
|
||||
def draw_image(self, image: Image, position: Position):
|
||||
self.surface.blit(image.surface, position.as_tuple())
|
||||
def draw_inside(self, drawable: Self, position: Position):
|
||||
self.surface.blit(drawable.surface, position.as_tuple())
|
||||
|
||||
def clear(self, color = Color(0, 0, 0)):
|
||||
self.surface.fill(color.as_tuple())
|
||||
|
@ -67,6 +77,23 @@ class Canvas:
|
|||
|
||||
return image
|
||||
|
||||
def get_dimensions(self) -> Dimensions:
|
||||
return Dimensions.from_tuple(self.surface.get_size())
|
||||
|
||||
|
||||
class Image(Drawable):
|
||||
def __init__(self, path = None):
|
||||
Drawable.__init__(self)
|
||||
|
||||
if path is not None:
|
||||
self.surface = pygame.image.load(path)
|
||||
|
||||
|
||||
class Canvas(Drawable):
|
||||
def __init__(self, size: Dimensions):
|
||||
Drawable.__init__(self, size)
|
||||
|
||||
|
||||
class Screen:
|
||||
def __init__(self, title: str, resolution: Dimensions):
|
||||
self.resolution = resolution
|
||||
|
@ -82,7 +109,7 @@ class Screen:
|
|||
def get_image(self):
|
||||
image = Image()
|
||||
|
||||
def update(self):
|
||||
def update(self) -> None:
|
||||
pygame.dispay.flip()
|
||||
|
||||
@staticmethod
|
||||
|
@ -99,11 +126,12 @@ class Visualization():
|
|||
def __init__(self, surface: Canvas, position: Position):
|
||||
self.surface = surface
|
||||
self.position = position
|
||||
self.on_hover = lambda : None
|
||||
|
||||
def draw(self, canvas: Canvas):
|
||||
canvas.draw_image(self.surface, self.position)
|
||||
def draw(self, canvas: Canvas) -> None:
|
||||
canvas.draw_inside(self.surface, self.position)
|
||||
|
||||
def is_inside(self, position: Position):
|
||||
def is_inside(self, position: Position) -> bool:
|
||||
if position.x < self.position.x:
|
||||
return False
|
||||
|
||||
|
@ -122,45 +150,79 @@ class Visualization():
|
|||
|
||||
|
||||
class Clickable(Visualization):
|
||||
def __init__(self, surface: Canvas, position: Position, on_click_callback: callable):
|
||||
Visualization.__init__(self, surface)
|
||||
self.on_click = on_click_callback
|
||||
def __init__(self, surface: Canvas, position: Position):
|
||||
Visualization.__init__(self, surface, position)
|
||||
self.on_click = lambda : None
|
||||
|
||||
def click(self):
|
||||
def click(self) -> None:
|
||||
self.on_click()
|
||||
|
||||
|
||||
class Dragable(Clickable):
|
||||
def __init__(self, canvas: Canvas, position: Position):
|
||||
Clickable.__init__(self, canvas, position)
|
||||
self.on_drag = lambda : None
|
||||
self.on_drop = lambda : None
|
||||
self.on_fail = lambda drag : None
|
||||
|
||||
def drag(self) -> None:
|
||||
self.on_drag()
|
||||
|
||||
def drop(self) -> None:
|
||||
self.on_drop()
|
||||
|
||||
def fail(self, drag: 'Drag'):
|
||||
self.on_fail(drag)
|
||||
|
||||
|
||||
class Dropable(Visualization):
|
||||
def __init__(self, canvas: Canvas, position: Position):
|
||||
Visualization.__init__(self, canvas, position)
|
||||
self.on_drop = lambda dragable: None
|
||||
|
||||
def drop(self, dragable: Dragable) -> None:
|
||||
self.on_drop(dragable)
|
||||
|
||||
|
||||
class Container(Visualization):
|
||||
ROWS = 0
|
||||
COLUMNS = 1
|
||||
|
||||
def __init__(self, position: Position, dimensions: Dimensions):
|
||||
Visualization.__init__(self, Canvas(dimensions), position)
|
||||
|
||||
self.background_image = None
|
||||
self.background_color = Color(0, 0, 0)
|
||||
self.visualizations = []
|
||||
self.clickables = []
|
||||
|
||||
def set_background_image(self, image: Image):
|
||||
def set_background_image(self, image: Image) -> None:
|
||||
self.background_image = image
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
self.surface.clear(self.background_color)
|
||||
|
||||
if self.background_image is not None:
|
||||
self.surface.draw_image(self.background_image, Dimensions(0, 0))
|
||||
self.surface.draw_inside(self.background_image, Dimensions(0, 0))
|
||||
|
||||
for visualization in self.visualizations:
|
||||
visualization.draw(self.surface)
|
||||
|
||||
def add_visualization(self, visualization: Visualization):
|
||||
def add_visualization(self, visualization: Visualization) -> None:
|
||||
self.visualizations.append(visualization)
|
||||
|
||||
def add_clickable(self, clickable: Clickable):
|
||||
def add_clickable(self, clickable: Clickable) -> None:
|
||||
self.add_visualization(clickable)
|
||||
self.clickables.append(clickable)
|
||||
|
||||
|
||||
class Drag:
|
||||
def __init__(self, dragable: Dragable, cursor_offset: Position):
|
||||
self.dragable = dragable
|
||||
self.origin = dragable.position.get_duplicate()
|
||||
self.cursor_offset = cursor_offset
|
||||
|
||||
|
||||
class UI:
|
||||
def __init__(self, title: str, resolution: Dimensions):
|
||||
pygame.init()
|
||||
|
@ -169,7 +231,7 @@ class UI:
|
|||
self.clock = pygame.time.Clock()
|
||||
self.screen = Screen(title, resolution)
|
||||
self.canvas = self.screen.get_canvas()
|
||||
|
||||
self.drag = None
|
||||
self.containers = []
|
||||
|
||||
def init(self):
|
||||
|
@ -182,30 +244,123 @@ class UI:
|
|||
container.render()
|
||||
container.draw(self.canvas)
|
||||
|
||||
pygame.display.update()
|
||||
|
||||
def add_container(self, container: Container):
|
||||
self.containers.append(container)
|
||||
|
||||
def exit(self):
|
||||
pygame.quit()
|
||||
self.is_running = False
|
||||
|
||||
def main(self):
|
||||
while self.is_running:
|
||||
self.clock.tick()
|
||||
event = pygame.event.wait()
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
if event.type == pygame.QUIT:
|
||||
self.exit()
|
||||
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
self.exit()
|
||||
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
self.exit()
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||
self._handle_click(event)
|
||||
|
||||
pygame.display.update()
|
||||
elif event.type == pygame.MOUSEBUTTONUP:
|
||||
self._handle_drop(event)
|
||||
|
||||
elif event.type == pygame.MOUSEMOTION:
|
||||
self._handle_mouse_motion(event)
|
||||
|
||||
self.update()
|
||||
|
||||
pygame.quit()
|
||||
|
||||
def _handle_click(self, event: pygame.event.Event) -> None:
|
||||
clickables_clicked = self._get_clickables_clicked(event)
|
||||
|
||||
if len(clickables_clicked) == 0:
|
||||
return
|
||||
|
||||
clickable = clickables_clicked[-1]
|
||||
|
||||
if type(clickable) is Dragable:
|
||||
self.drag = Drag(
|
||||
clickable,
|
||||
Position.from_tuple(event.pos) - clickable.position
|
||||
)
|
||||
|
||||
clickable.click()
|
||||
|
||||
def _handle_drag(self, event: pygame.event.Event):
|
||||
position_cursor = Position.from_tuple(event.pos)
|
||||
|
||||
position = position_cursor - self.drag.cursor_offset
|
||||
|
||||
self.drag.dragable.position = position
|
||||
self.drag.dragable.drag()
|
||||
|
||||
def _handle_drop(self, event: pygame.event.Event):
|
||||
if self.drag is None:
|
||||
return
|
||||
|
||||
position_cursor = Position.from_tuple(event.pos)
|
||||
|
||||
dropables_hovered = self._get_visualizations_hovered(position_cursor, [Dropable])
|
||||
|
||||
if len(dropables_hovered) == 0:
|
||||
self.drag.dragable.fail(self.drag)
|
||||
self.drag.dragable.position = self.drag.origin
|
||||
else:
|
||||
dropables_hovered[-1].drop(self.drag.dragable)
|
||||
self.drag.dragable.drop(dropables_hovered[-1])
|
||||
|
||||
self.drag = None
|
||||
|
||||
def _handle_mouse_motion(self, event: pygame.event.Event):
|
||||
if type(self.drag) is Drag:
|
||||
self._handle_drag(event)
|
||||
|
||||
def _get_visualizations_hovered(self, cursor_position: Position, filter: list = []):
|
||||
containers_hovered = []
|
||||
visualizations_hovered = []
|
||||
|
||||
for container in self.containers:
|
||||
if not container.is_inside(cursor_position):
|
||||
continue
|
||||
|
||||
containers_hovered.append(container)
|
||||
|
||||
for container_hovered in containers_hovered:
|
||||
for visualization in container_hovered.visualizations:
|
||||
if not self._is_in_filter(visualization, filter):
|
||||
continue
|
||||
|
||||
if visualization.is_inside(cursor_position):
|
||||
visualizations_hovered.append(visualization)
|
||||
|
||||
return visualizations_hovered
|
||||
|
||||
def _get_clickables_clicked(self, event: pygame.event.Event):
|
||||
return self._get_visualizations_hovered(
|
||||
Position.from_tuple(event.pos),
|
||||
[Clickable, Dragable]
|
||||
)
|
||||
|
||||
def _is_in_filter(self, visualization: Visualization, filter: list = []) -> bool:
|
||||
if len(filter) == 0:
|
||||
return True
|
||||
|
||||
for f in filter:
|
||||
for parent in visualization.__class__.mro():
|
||||
if parent is f:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class IconButton(Clickable):
|
||||
def __init__(self, icon: pygame.Surface, icon_hover = None):
|
||||
def __init__(self, icon: Image, icon_hover = None):
|
||||
self.icon = icon
|
||||
self.icon_hover = icon_hover
|
||||
|
||||
|
@ -218,7 +373,13 @@ class ResourceContainer:
|
|||
self.images[name] = Image(url)
|
||||
|
||||
def get_image(self, name):
|
||||
return self.images[name]
|
||||
return self._get_resource(self.images, name)
|
||||
|
||||
def _get_resource(self, resources: list, name: str):
|
||||
try:
|
||||
return resources[name]
|
||||
except KeyError:
|
||||
raise Exception('Resource "%s" not found!' % (name))
|
||||
|
||||
@staticmethod
|
||||
def from_json_file(json_file):
|
||||
|
|
Loading…
Reference in New Issue