Skip to content

Commit

Permalink
Merge pull request #232 from vkottler/dev/4.6.0
Browse files Browse the repository at this point in the history
Working on more UI controls
  • Loading branch information
vkottler authored Jun 17, 2024
2 parents b5c9a8c + a7e87d1 commit 3a81cf5
Show file tree
Hide file tree
Showing 37 changed files with 701 additions and 119 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- run: |
mk python-release owner=vkottler \
repo=runtimepy version=4.5.1
repo=runtimepy version=4.6.0
if: |
matrix.python-version == '3.11'
&& matrix.system == 'ubuntu-latest'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
=====================================
generator=datazen
version=3.1.4
hash=c9e8e0a4b1765408663652c02a32e75e
hash=f51cb5056fa27a5d20fe9849b331acf6
=====================================
-->

# runtimepy ([4.5.1](https://pypi.org/project/runtimepy/))
# runtimepy ([4.6.0](https://pypi.org/project/runtimepy/))

[![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
Expand Down
2 changes: 1 addition & 1 deletion local/configs/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ entry: {{entry}}

requirements:
- vcorelib>=3.2.4
- svgen>=0.6.6
- svgen>=0.6.7
- websockets
- psutil
- "windows-curses; sys_platform == 'win32' and python_version < '3.12'"
Expand Down
4 changes: 2 additions & 2 deletions local/variables/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
major: 4
minor: 5
patch: 1
minor: 6
patch: 0
entry: runtimepy
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__"

[project]
name = "runtimepy"
version = "4.5.1"
version = "4.6.0"
description = "A framework for implementing Python services."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
4 changes: 2 additions & 2 deletions runtimepy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =====================================
# generator=datazen
# version=3.1.4
# hash=4c10d97db8d952d1b242acfcfd80abb4
# hash=724dfbb16b33acb76a075be8d0b24fbd
# =====================================

"""
Expand All @@ -10,7 +10,7 @@

DESCRIPTION = "A framework for implementing Python services."
PKG_NAME = "runtimepy"
VERSION = "4.5.1"
VERSION = "4.6.0"

# runtimepy-specific content.
METRICS_NAME = "metrics"
Expand Down
45 changes: 45 additions & 0 deletions runtimepy/channel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@
from runtimepy.primitives.types import AnyPrimitiveType as _AnyPrimitiveType
from runtimepy.registry.item import RegistryItem as _RegistryItem

Literal = int | float | bool
Default = _Optional[Literal]
Controls = dict[str, Literal | dict[str, Literal]]


class Channel(_RegistryItem, _EnumMixin, _Generic[_T]):
"""An interface for an individual channel."""

raw: _T
default: Default

def __str__(self) -> str:
"""Get this channel as a string."""
return f"{self.id}: {self.raw}"
Expand All @@ -47,6 +54,29 @@ def type(self) -> _AnyPrimitiveType:
"""Get the underlying primitive type of this channel."""
return self.raw.kind

def update_primitive(self, primitive: _T) -> None:
"""Use a new underlying primitive for this channel."""

assert isinstance(primitive, type(self.raw))
self.raw = primitive

# Update other runtime pieces.
self.event.primitive = self.raw
self.set_default()

def set_default(self, default: Default = None) -> None:
"""Set a new default value for this channel."""

if default is not None:
self.default = default

if self.default is not None:
assert self.raw.valid_primitive(self.default), (
self.raw,
self.default,
)
self.raw.value = self.default # type: ignore

def init(self, data: _JsonObject) -> None:
"""Perform implementation-specific initialization."""

Expand All @@ -55,6 +85,16 @@ def init(self, data: _JsonObject) -> None:
# This is the underlying storage entity for this channel.
self.raw: _T = _cast(_T, _normalize(_cast(str, data["type"]))())

_ctl: _Optional[Controls] = data.get("controls") # type: ignore
self.controls: _Optional[Controls] = _ctl

# Handle a possible default value.
backup = None
self.default = backup
if _ctl:
backup = _ctl.get("default")
self.set_default(data.get("default", backup)) # type: ignore

# Whether or not this channel should accept commands.
self.commandable: bool = _cast(bool, data["commandable"])

Expand All @@ -78,8 +118,13 @@ def asdict(self) -> _JsonObject:
if self._enum is not None:
result["enum"] = self._enum

if self.controls:
result["controls"] = self.controls

if self.description:
result["description"] = self.description
if self.default is not None:
result["default"] = self.default

return _cast(_JsonObject, result)

Expand Down
7 changes: 7 additions & 0 deletions runtimepy/channel/environment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Iterator as _Iterator

# internal
from runtimepy.channel import Default as _Default
from runtimepy.channel.environment.array import (
ArrayChannelEnvironment as _ArrayChannelEnvironment,
)
Expand All @@ -32,3 +33,9 @@ class ChannelEnvironment(
def names(self) -> _Iterator[str]:
"""Iterate over registered names in the environment."""
yield from self.channels.names.names

def set_default(self, key: str, default: _Default) -> None:
"""Set a new default value for a channel."""

chan, _ = self[key]
chan.default = default
1 change: 1 addition & 0 deletions runtimepy/channel/environment/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def sample_env(env: ChannelEnvironment = None) -> ChannelEnvironment:
"really_really_long_enum",
enum="InsanelyLongEnumNameForTesting",
commandable=True,
default=2,
)
env.bool_channel("bool")
env.int_channel("int", commandable=True)
Expand Down
15 changes: 13 additions & 2 deletions runtimepy/channel/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# internal
from runtimepy.channel import AnyChannel as _AnyChannel
from runtimepy.channel import Channel as _Channel
from runtimepy.channel import Default as _Default
from runtimepy.channel.event.header import PrimitiveEventHeader
from runtimepy.codec.protocol import Protocol
from runtimepy.mapping import DEFAULT_PATTERN
Expand All @@ -28,6 +29,7 @@
from runtimepy.registry import Registry as _Registry
from runtimepy.registry.name import NameRegistry as _NameRegistry
from runtimepy.registry.name import RegistryKey as _RegistryKey
from runtimepy.ui.controls import Controlslike, normalize_controls


class ChannelNameRegistry(_NameRegistry):
Expand Down Expand Up @@ -90,6 +92,9 @@ def channel(
enum: _RegistryKey = None,
scaling: ChannelScaling = None,
description: str = None,
default: _Default = None,
controls: Controlslike = None,
**kwargs,
) -> _Optional[_AnyChannel]:
"""Create a new channel."""

Expand All @@ -111,19 +116,25 @@ def channel(
data: _JsonObject = {
"type": str(primitive.kind),
"commandable": commandable,
**kwargs,
}
if enum is not None:
data["enum"] = enum

if description:
data["description"] = description

if default is not None:
data["default"] = default

if controls:
data["controls"] = normalize_controls(controls) # type: ignore

result = self.register_dict(name, data)

# Replace the underlying primitive, in case it was direclty passed in.
if result is not None:
result.raw = primitive
result.event.primitive = primitive # type: ignore
result.update_primitive(primitive)

return result

Expand Down
8 changes: 8 additions & 0 deletions runtimepy/data/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ body > :first-child {
.stale {
color: var(--bs-warning-text-emphasis) !important;
}

.slider {
min-width: 8em;
}

.toggle-value {
/* placeholder */
}
12 changes: 12 additions & 0 deletions runtimepy/data/js/classes/ChannelTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class ChannelTable {
this.rows[child.id] = child.querySelector(".channel-value");
}

/* A mapping of channel names to a possible slider element (populated
* externally). */
this.sliders = {};

/* Initialize input boxes. */
this.channelInputs = {};
for (let input of table.querySelectorAll("input.channel-value-input")) {
Expand Down Expand Up @@ -68,6 +72,14 @@ class ChannelTable {
this.channelInputs[key].value = val;
}

/* Update slider if one exists. */
if (key in this.sliders) {
let elem = this.sliders[key];
if (!elem.classList.contains("moving")) {
elem.value = val;
}
}

/*
* This is here so that a value flapping between positive and negative
* (minus sign appearing and disappearing) doesn't look bad.
Expand Down
35 changes: 28 additions & 7 deletions runtimepy/data/js/classes/PlotDrawer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class PlotDrawer {
constructor(canvas) {
constructor(canvas, colors) {
this.canvas = canvas;

// this.wglp = new WebglPlotBundle.WebglPlot(this.canvas, {debug : true});
Expand All @@ -11,6 +11,8 @@ class PlotDrawer {

/* Point managers for individual channels. */
this.channels = {};
this.colors = colors;
this.rgbaColors = {};

/* Line objects by channel name. */
this.lines = {};
Expand Down Expand Up @@ -79,11 +81,27 @@ class PlotDrawer {
}
}

setColor(key, rgb) {
this.rgbaColors[key] =
new WebglPlotBundle.ColorRGBA(rgb.r / 255, rgb.g / 255, rgb.b / 255, 1);
}

newLine(key) {
/* Random color. */
const color = new WebglPlotBundle.ColorRGBA(Math.random(), Math.random(),
Math.random(), 1);
this.lines[key] = new WebglPlotBundle.WebglLine(color, this.canvas.width);
/* Get color for line. */
if (!(key in this.rgbaColors)) {
if (key in this.colors) {
this.setColor(key, this.colors[key]);
} else {
this.setColor(key, {
r : Math.random() * 255,
g : Math.random() * 255,
b : Math.random() * 255
});
}
}

this.lines[key] =
new WebglPlotBundle.WebglLine(this.rgbaColors[key], this.canvas.width);

if (key in this.channels) {
this.channels[key].draw(this.lines[key]);
Expand All @@ -100,13 +118,16 @@ class PlotDrawer {
}
}

updateSize() {
updateAllLines() {
/* Re-add all lines. */
for (let key in this.lines) {
this.newLine(key);
}
this.updateLines();
}

updateSize() {
this.updateAllLines();
this.wglp.viewport(0, 0, this.canvas.width, this.canvas.height);
}

Expand All @@ -115,7 +136,7 @@ class PlotDrawer {
let chan = this.channels[name];

/* Make configurable at some point? */
chan.buffer.bumpCapacity(wheelDelta > 0);
chan.buffer.bumpCapacity(wheelDelta < 0);

chan.draw(this.lines[name]);
}
Expand Down
Loading

0 comments on commit 3a81cf5

Please sign in to comment.