Skip to content

Commit

Permalink
Moved round_down into geometry module.
Browse files Browse the repository at this point in the history
  • Loading branch information
salt-die committed Mar 9, 2024
1 parent 4b7822a commit 2d17287
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 59 deletions.
15 changes: 1 addition & 14 deletions src/batgrl/gadgets/gadget.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from numpy.typing import NDArray

from .. import easings
from ..geometry import Point, Region, Size, clamp, lerp
from ..geometry import Point, Region, Size, clamp, lerp, round_down
from ..io import KeyEvent, MouseEvent, PasteEvent
from .text_tools import Cell, cell

Expand All @@ -38,19 +38,6 @@
_UID = count(1)


def round_down(n: float) -> int:
"""
Like the built-in `round`, but always rounds down.
This is used instead of `round` so that small changes in gadget geometry don't cause
the gadget to "jump" around when hints are applied.
"""
i, r = divmod(n, 1)
if r <= 0.5:
return int(i)
return int(i + 1)


Anchor = Literal[
"top-left",
"top",
Expand Down
106 changes: 61 additions & 45 deletions src/batgrl/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,64 @@
from bisect import bisect
from dataclasses import dataclass, field
from numbers import Real
from operator import and_, or_, xor
from typing import Callable, Iterator, NamedTuple

__all__ = [
"clamp",
"lerp",
"round_down",
"Point",
"Size",
"Rect",
"Region",
]


def clamp(value: Real, min: Real | None, max: Real | None) -> Real:
"""
If `value` is less than `min`, returns `min`; otherwise if `max` is less than
`value`, returns `max`; otherwise returns `value`. A one-sided clamp is possible by
setting `min` or `max` to `None`.
Parameters
----------
value : Real
Value to clamp.
min : Real | None
Minimum of clamped value.
max : Real | None
Maximum of clamped value.
Returns
-------
Real
A value between `min` and `max`, inclusive.
"""
if min is not None and value < min:
return min

if max is not None and value > max:
return max

return value


def lerp(a: Real, b: Real, p: Real) -> Real:
"""Linear interpolation of `a` to `b` with proportion `p`."""
return (1.0 - p) * a + p * b


def round_down(n: float) -> int:
"""
Like the built-in `round`, but always rounds down.
Used instead of `round` for smoother geometry.
"""
i, r = divmod(n, 1)
if r <= 0.5:
return int(i)
return int(i) + 1


class Point(NamedTuple):
"""
Expand Down Expand Up @@ -93,45 +148,6 @@ def columns(self):
return self.width


def clamp(value: Real, min: Real | None, max: Real | None) -> Real:
"""
If `value` is less than `min`, returns `min`; otherwise if `max` is less than
`value`, returns `max`; otherwise returns `value`. A one-sided clamp is possible by
setting `min` or `max` to `None`.
Parameters
----------
value : Real
Value to clamp.
min : Real | None
Minimum of clamped value.
max : Real | None
Maximum of clamped value.
Returns
-------
Real
A value between `min` and `max`, inclusive.
"""
if min is not None and value < min:
return min

if max is not None and value > max:
return max

return value


def lerp(a: Real, b: Real, p: Real) -> Real:
"""Linear interpolation of `a` to `b` with proportion `p`."""
return (1.0 - p) * a + p * b


def sub(a: bool, b: bool) -> bool:
"""Return `a` and not `b`."""
return a and not b


@dataclass(slots=True)
class Rect:
"""
Expand Down Expand Up @@ -441,19 +457,19 @@ def _merge_regions(
# TODO: in-place merge and iand, ior, iadd, isub, ixor methods

def __and__(self, other: "Region") -> "Region":
return self._merge_regions(other, and_)
return self._merge_regions(other, lambda a, b: a & b)

def __or__(self, other: "Region") -> "Region":
return self._merge_regions(other, or_)
return self._merge_regions(other, lambda a, b: a | b)

def __add__(self, other: "Region") -> "Region":
return self._merge_regions(other, or_)
return self._merge_regions(other, lambda a, b: a | b)

def __sub__(self, other: "Region") -> "Region":
return self._merge_regions(other, sub)
return self._merge_regions(other, lambda a, b: a & (not b))

def __xor__(self, other: "Region") -> "Region":
return self._merge_regions(other, xor)
return self._merge_regions(other, lambda a, b: a ^ b)

def __bool__(self):
return len(self.bands) > 0
Expand Down

0 comments on commit 2d17287

Please sign in to comment.