diff --git a/dariuino-ide.py b/dariuino-ide.py index b6c46ef..73a5f36 100755 --- a/dariuino-ide.py +++ b/dariuino-ide.py @@ -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() diff --git a/res/graphics/icon-for-32.png b/res/graphics/icon-for-32.png new file mode 100644 index 0000000..73f1fd7 Binary files /dev/null and b/res/graphics/icon-for-32.png differ diff --git a/res/graphics/icon-while-32.png b/res/graphics/icon-while-32.png new file mode 100644 index 0000000..3220270 Binary files /dev/null and b/res/graphics/icon-while-32.png differ diff --git a/res/resources.json b/res/resources.json index 7657711..e1baabb 100644 --- a/res/resources.json +++ b/res/resources.json @@ -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" } } diff --git a/src/dariuino.py b/src/dariuino.py new file mode 100644 index 0000000..526793c --- /dev/null +++ b/src/dariuino.py @@ -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 + diff --git a/src/gui.py b/src/gui.py index 57c23b4..1126674 100755 --- a/src/gui.py +++ b/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):