Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thumbnail buttons #7

Merged
merged 7 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 100 additions & 13 deletions battle_map_tv/gui_elements.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import os.path
import typing
from typing import Callable, Union, Optional

import cv2
import pyglet
from battle_map_tv.storage import set_in_storage, StorageKeys, get_from_storage, remove_from_storage
from pyglet.graphics import Batch
from pyglet.text import Label

from battle_map_tv.opencv_utils import opencv_to_pyglet_image, change_brightness

if typing.TYPE_CHECKING:
from battle_map_tv.window_image import ImageWindow

margin_y_label = 10

pyglet.resource.path = [os.path.join(pyglet.resource.get_script_home(), "resources")]
Expand Down Expand Up @@ -51,17 +59,17 @@ def __init__(
)
self.y_original = y
self.height = 30
self.label = Label(text=label, x=self.x, batch=batch)
self.label = Label(text=label, x=self.x, y=self.y2 + margin_y_label, batch=batch)

def hide(self):
self.y = -100
self.label.y = -100
self.label.visible = False

def show(self):
self.y = self.y_original
self._layout.x = self.x + 10
self._layout.y = self.y - 5
self.label.y = self.y2 + margin_y_label
self.label.visible = True


class PushButton(CoordinatesMixin, pyglet.gui.PushButton):
Expand Down Expand Up @@ -106,10 +114,12 @@ def __init__(self, x: int, y: int, batch: Batch, callback: Callable, effect: str
self.set_handler("on_toggle", callback)

def hide(self):
self.y = -100
self.enabled = False
self._sprite.visible = False

def show(self):
self.y = self.y_original
self._sprite.visible = True
self.enabled = True


class TabButton(CoordinatesMixin, pyglet.gui.PushButton):
Expand Down Expand Up @@ -140,6 +150,79 @@ def __init__(
self.set_handler("on_release", callback)


class ThumbnailButton(CoordinatesMixin, pyglet.gui.ToggleButton):
width: int = 100
height: int = 100

def __init__(
self,
index: int,
x: int,
y: int,
batch: Batch,
image_window: "ImageWindow",
all_thumbnail_buttons: list["ThumbnailButton"],
):
self.index = index
self.image_path: Optional[str] = None
self.image_window = image_window
self.all_thumbnail_buttons = all_thumbnail_buttons
button_img = pyglet.resource.image("button_file_drop.png")
super().__init__(x=x, y=y, pressed=button_img, depressed=button_img, batch=batch)
image_path = get_from_storage(self._storage_key, optional=True)
if image_path is not None:
if os.path.exists(image_path):
self.add_thumbnail_image(image_path)
else:
remove_from_storage(self._storage_key)
self.set_handler("on_toggle", self._custom_on_toggle)

def _custom_on_toggle(self, value):
if self.image_path is None:
return
if value:
for thumbnail_button in self.all_thumbnail_buttons:
if thumbnail_button is not self and thumbnail_button.value:
thumbnail_button.value = False
self.image_window.add_image(self.image_path)
else:
self.image_window.remove_image()

@property
def _storage_key(self) -> StorageKeys:
return StorageKeys[f"thumbnail_{self.index}"]

def hide(self):
self.enabled = False
self._sprite.visible = False

def show(self):
self._sprite.visible = True
self.enabled = True

def on_file_drop(self, x: int, y: int, image_path: str) -> bool:
if not self.enabled or not self._check_hit(x, y):
return False
self.add_thumbnail_image(image_path)
if self.value:
self.image_window.add_image(image_path)
set_in_storage(self._storage_key, image_path)
return True

def add_thumbnail_image(self, image_path: str):
self.image_path = image_path
assert self.enabled
assert self._sprite.visible
image_cv = cv2.imread(image_path)
image_cv = cv2.resize(image_cv, (self.width, self.height), interpolation=cv2.INTER_AREA)
self._depressed_img = opencv_to_pyglet_image(image_cv)
image_cv_pressed = change_brightness(image_cv, -50)
self._pressed_img = opencv_to_pyglet_image(image_cv_pressed)
image_cv_hover = change_brightness(image_cv, 50)
self._hover_img = opencv_to_pyglet_image(image_cv_hover)
self._sprite.image = self._depressed_img


class Slider(CoordinatesMixin, pyglet.gui.Slider):
base = pyglet.resource.image("slider_base.png").get_texture()
knob = pyglet.resource.image("slider_knob.png").get_texture()
Expand Down Expand Up @@ -174,16 +257,17 @@ def __init__(
self.label = Label(
text=label,
x=self.x,
y=self.y2 + margin_y_label,
batch=batch,
)
self.label_value = Label(
text=label_formatter(default),
x=super().x2 + 20,
y=self.y + self.height / 2,
anchor_y="center",
batch=batch,
)
self.label_formatter = label_formatter
self.show()

@property
def x2(self) -> int:
Expand Down Expand Up @@ -215,12 +299,15 @@ def reset(self):
self.value = self.default

def hide(self):
self.y = -100
self.label.y = -100
self.label_value.y = -100
self.enabled = False
self._base_spr.visible = False
self._knob_spr.visible = False
self.label.visible = False
self.label_value.visible = False

def show(self):
self.y = self.y_original
self.label.y = self.y2 + margin_y_label
self.label_value.y = self.y + self.height / 2
self.value = self.value
self.enabled = True
self._base_spr.visible = True
self._knob_spr.visible = True
self.label.visible = True
self.label_value.visible = True
29 changes: 29 additions & 0 deletions battle_map_tv/opencv_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from io import BytesIO

import cv2
import pyglet
from pyglet.image import AbstractImage


def change_brightness(img, value: int):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)

if value > 0:
lim = 255 - value
v[v > lim] = 255
v[v <= lim] += value
elif value < 0:
lim = 0 - value
v[v < lim] = 0
v[v >= lim] = v[v >= lim] + value

final_hsv = cv2.merge((h, s, v))
img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
return img


def opencv_to_pyglet_image(opencv_image) -> AbstractImage:
as_bytes = cv2.imencode(".png", opencv_image)[1].tobytes()
image = pyglet.image.load(filename=".png", file=BytesIO(as_bytes))
return image
Binary file added battle_map_tv/resources/button_file_drop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions battle_map_tv/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class StorageKeys(Enum):
width_mm = "width_mm"
height_mm = "height_mm"
previous_image = "previous_image"
thumbnail_0 = "thumbnail_0"
thumbnail_1 = "thumbnail_1"
thumbnail_2 = "thumbnail_2"
thumbnail_3 = "thumbnail_3"


def get_from_storage(key: StorageKeys, optional: bool = False):
Expand Down
60 changes: 53 additions & 7 deletions battle_map_tv/window_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
PushButton,
TabButton,
EffectToggleButton,
ThumbnailButton,
)
from battle_map_tv.scale_detection import find_image_scale
from battle_map_tv.storage import get_from_storage, StorageKeys, set_in_storage
Expand All @@ -37,6 +38,8 @@ def __init__(self, image_window: ImageWindow, *args, **kwargs):
self.batch_background = Batch()
self.frame = Frame(window=self)

self.thumbnail_buttons: List[ThumbnailButton] = []

row_y = margin_y

def slider_scale_callback(value: Union[float, str]):
Expand Down Expand Up @@ -90,11 +93,17 @@ def button_callback_autoscale(button_value: bool) -> bool:

row_y += 100

def button_callback_remove():
for thumbnail_button in self.thumbnail_buttons:
if thumbnail_button.value:
thumbnail_button.value = False
image_window.remove_image()

self.button_remove_image = PushButton(
x=margin_x,
y=row_y,
batch=self.batch,
callback=lambda: image_window.remove_image(),
callback=button_callback_remove,
label="Remove",
icon="remove",
)
Expand Down Expand Up @@ -194,32 +203,42 @@ def button_callback_grid(button_value: bool) -> bool:

self.tab_buttons: List[TabButton] = []

self._add_tab_images(tab_index=0, row_y=row_y)

self.text_entry_screen_width: TextEntry
self.text_entry_screen_height: TextEntry
self._add_tab_screen_size(tab_index=0, row_y=row_y)
self._add_tab_screen_size(tab_index=1, row_y=row_y)

self.slider_grid_opacity: Slider
self._add_tab_grid_opacity(tab_index=1, row_y=row_y)
self._add_tab_grid_opacity(tab_index=2, row_y=row_y)

self.effect_buttons: List[EffectToggleButton] = []
self._add_tab_effects(tab_index=2, row_y=row_y)
self._add_tab_effects(tab_index=3, row_y=row_y)

# Start with showing the first tab
self._hide_tab_content()
self.text_entry_screen_width.show()
self.text_entry_screen_height.show()
for thumbnail in self.thumbnail_buttons:
thumbnail.show()

def on_draw(self):
self.clear()
self.batch_background.draw()
self.batch.draw()

def on_file_drop(self, x: int, y: int, paths: List[str]):
self.image_window.add_image(image_path=paths[0])
self.switch_to()
for thumbnail in self.thumbnail_buttons:
is_hit = thumbnail.on_file_drop(x=x, y=y, image_path=paths[0])
if is_hit:
break
else:
self.image_window.add_image(image_path=paths[0])
self.switch_to()
self.slider_scale.reset()

def _hide_tab_content(self):
for thumbnail in self.thumbnail_buttons:
thumbnail.hide()
self.text_entry_screen_width.hide()
self.text_entry_screen_height.hide()
self.slider_grid_opacity.hide()
Expand Down Expand Up @@ -331,3 +350,30 @@ def callback_tab_effects():
callback=callback_tab_effects,
label="Effects",
)

def _add_tab_images(self, tab_index: int, row_y: int):
thumbnail_y = row_y + (tab_height - ThumbnailButton.height) // 2
for i in range(4):
thumbnail_button = ThumbnailButton(
index=i,
x=(2 + i) * margin_x + i * ThumbnailButton.width,
y=thumbnail_y,
batch=self.batch,
image_window=self.image_window,
all_thumbnail_buttons=self.thumbnail_buttons,
)
self.thumbnail_buttons.append(thumbnail_button)
self.frame.add_widget(thumbnail_button)

def callback_tab_images():
self.switch_to()
self._hide_tab_content()
for thumbnail in self.thumbnail_buttons:
thumbnail.show()

self._create_tab_button(
tab_index=tab_index,
row_y=row_y,
callback=callback_tab_images,
label="Images",
)
Loading