-
Notifications
You must be signed in to change notification settings - Fork 15
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
Add Canvas
and OpenGLCanvas
widgets
#121
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from .canvas import Canvas | ||
from .enums import FillRule | ||
from .equipment import Brush, Pen | ||
from .items import ( | ||
CanvasItem, | ||
Circle, | ||
Ellipse, | ||
Group, | ||
Image, | ||
Line, | ||
Path, | ||
Polygon, | ||
PolygonalLine, | ||
Rectangle, | ||
Square, | ||
Text, | ||
Transform, | ||
Vector, | ||
) | ||
from .transform import Transform | ||
from .utils import ArrowHead, DashPattern |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from __future__ import annotations | ||
|
||
from abc import ABC, abstractmethod | ||
from dataclasses import dataclass | ||
|
||
from tukaan._base import TkWidget, WidgetBase | ||
from tukaan._tcl import Tcl | ||
from .equipment import Brush, Pen | ||
from .gradient import LinearGradient, RadialGradient, Gradient | ||
from tukaan.colors import Color | ||
|
||
|
||
class Canvas(WidgetBase): | ||
_tcl_class = "::tkp::canvas" | ||
|
||
def __init__( | ||
self, | ||
parent: TkWidget, | ||
*, | ||
width: int | None = 400, | ||
height: int | None = 400, | ||
bg_color: Color | str | None = None, | ||
) -> None: | ||
super().__init__( | ||
parent, | ||
width=width, | ||
height=height, | ||
background=bg_color, | ||
borderwidth=0, | ||
highlightthickness=0, | ||
) | ||
|
||
self.brush = Brush() | ||
self.pen = Pen() | ||
|
||
self._gradient_superclass = Gradient(self) | ||
self.linear_gradient = LinearGradient(self) | ||
self.radial_gradient = RadialGradient(self) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is super unintuitive, and makes no sense. Unfortunately we can't make it totally independent from the camvas wodget, because that's not how TkPath works, but it shouldn't be a "method". Plus another thing came into my mind, that's actually a serious problem. There are random helper objects that are mutable and there are others that are immutable. This is because the objects that are mutable are actual Tk stuff, and others are just simple Python objects, e.g dataclasses, which can't notify the widget is an attribute is changed. This is NOT good. Something must be done. Mutable:
Immutable
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from enum import Enum | ||
|
||
class FillRule(Enum): | ||
EvenOdd = "evenodd" | ||
NonZero = "nonzero" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
|
||
from tukaan._tcl import Tcl | ||
from tukaan.colors import Color | ||
|
||
from .utils import DashPattern | ||
|
||
|
||
class Missing: | ||
... | ||
|
||
|
||
def get_missing_or_none(value): | ||
if value is Missing: | ||
return None | ||
elif value is None: | ||
return "" | ||
else: | ||
return value | ||
|
||
|
||
@dataclass(frozen=True) | ||
class Brush: | ||
fill: Color | str | None | Missing = Missing | ||
opacity: float = None | ||
fillrule: str = None | ||
|
||
def __to_tcl__(self) -> tuple: | ||
return Tcl.to_tcl_args( | ||
fill=get_missing_or_none(self.fill), | ||
fillopacity=self.opacity, | ||
fillrule=self.fillrule, | ||
) | ||
|
||
|
||
@dataclass(frozen=True) | ||
class Pen: | ||
color: Color | str | None | Missing = Missing | ||
width: float = None | ||
pattern: DashPattern = Missing | ||
# opacity: float = None # causes segfaults in TkPath | ||
line_cap: str = None | ||
line_join: str = None | ||
# miterlimit: float = None # buggy! | ||
|
||
def __to_tcl__(self) -> tuple: | ||
return Tcl.to_tcl_args( | ||
stroke=get_missing_or_none(self.color), | ||
strokedasharray=get_missing_or_none(self.pattern), | ||
strokelinecap=self.line_cap, | ||
strokelinejoin=self.line_join, | ||
# strokeopacity=self.opacity, | ||
strokewidth=self.width, | ||
# strokemiterlimit=self.miterlimit, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
from __future__ import annotations | ||
|
||
from math import cos, radians, sin, tan | ||
from typing import TYPE_CHECKING | ||
|
||
from tukaan._tcl import Tcl | ||
from tukaan._utils import seq_pairs | ||
|
||
if TYPE_CHECKING: | ||
from .canvas import Canvas | ||
|
||
|
||
class Gradient: | ||
def __init__(self, parent: Canvas) -> None: | ||
self._canvas = parent | ||
|
||
def __from_tcl__(self, gradient_name: str) -> Gradient: | ||
gradient = {"linear": LinearGradient, "radial": RadialGradient}[ | ||
Tcl.call(str, self._canvas, "gradient", "type", gradient_name) | ||
](self._canvas) | ||
gradient._name = gradient_name | ||
return gradient | ||
|
||
@property | ||
def in_use(self) -> bool: | ||
return Tcl.call(bool, self._canvas, "gradient", "inuse", self) | ||
|
||
@property | ||
def stops(self): | ||
result = Tcl.call((str,), self._canvas, "gradient", "cget", self, "-stops") | ||
|
||
result_dict = {} | ||
for x in result: | ||
k, v = x.split(" ") | ||
result_dict[float(k)] = v | ||
|
||
return result_dict | ||
|
||
@stops.setter | ||
def stops(self, new_stops) -> None: | ||
Tcl.call( | ||
None, | ||
self._canvas, | ||
"gradient", | ||
"configure", | ||
self, | ||
"-stops", | ||
self._process_stops(new_stops), | ||
) | ||
|
||
def dispose(self) -> None: | ||
Tcl.call(None, self._canvas, "gradient", "delete", self) | ||
|
||
def _process_stops(self, stops: list[str] | tuple[str, ...] | dict[int, str]): | ||
if not isinstance(stops, dict): | ||
step = 1 / (len(stops) - 1) | ||
stops = {(step * index): color for index, color in enumerate(stops)} | ||
|
||
result = list(seq_pairs(Tcl.to(stops))) | ||
result.sort(key=lambda e: e[0]) | ||
return result | ||
|
||
|
||
class LinearGradient(Gradient): | ||
def __call__( | ||
self, stops: list[str] | tuple[str, ...] | dict[int, str], *, angle: int = 0 | ||
) -> Gradient: | ||
angle_sin = sin(radians(angle)) | ||
angle_cos = cos(radians(angle)) | ||
|
||
if 0 <= angle <= 90: | ||
ltransition = (0, 0, angle_cos, angle_sin) | ||
elif 90 < angle <= 180: | ||
ltransition = (-angle_cos, 0, 0, angle_sin) | ||
elif 180 < angle <= 270: | ||
ltransition = (-angle_cos, -angle_sin, 0, 0) | ||
elif 270 < angle <= 360: | ||
ltransition = (0, -angle_sin, angle_cos, 0) | ||
else: | ||
raise ValueError("angle must be between 0 and 360") | ||
|
||
self._name = Tcl.call( | ||
str, | ||
self._canvas, | ||
"gradient", | ||
"create", | ||
"linear", | ||
"-stops", | ||
self._process_stops(stops), | ||
*Tcl.to_tcl_args(lineartransition=ltransition), | ||
) | ||
return self | ||
|
||
|
||
class RadialGradient(Gradient): | ||
def __call__( | ||
self, | ||
stops: list[str] | tuple[str, ...] | dict[int, str], | ||
*, | ||
center: int | tuple[int, int] = 0.5, | ||
last_center: int | tuple[int, int] = 0.5, | ||
radius: int = 0.5, | ||
) -> Gradient: | ||
if isinstance(center, (tuple, list)): | ||
fx, fy = center | ||
else: | ||
fx = fy = center | ||
|
||
if isinstance(last_center, (tuple, list)): | ||
cx, cy = last_center | ||
else: | ||
cx = cy = last_center | ||
|
||
self._name = Tcl.call( | ||
str, | ||
self._canvas, | ||
"gradient", | ||
"create", | ||
"radial", | ||
"-stops", | ||
self._process_stops(stops), | ||
*Tcl.to_tcl_args(radialtransition=(cx, cy, radius, fx, fy)), | ||
) | ||
return self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe revert this, and use a global tkfont instance to convert points to pixels in
get_font_options_for_text_item