From 95f4673f977214cd8b0598c30f29d454a584f420 Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Sat, 2 Nov 2024 18:06:50 +0100 Subject: [PATCH] Add cursor --- Cargo.lock | 186 +---------------------------- Cargo.toml | 6 +- pysrc/athenaCL/libATH/athenaObj.py | 42 ++++++- pysrc/athenaCL/libATH/command.py | 44 +++---- resources/img/pause.svg | 1 - resources/img/play.svg | 1 - src/app/app.rs | 80 +++++++++++-- src/app/midi_player.rs | 13 +- src/interpreter/athena_obj_ext.rs | 61 ++++++++++ src/interpreter/interpreter.rs | 72 ++++++----- src/interpreter/mod.rs | 1 + 11 files changed, 251 insertions(+), 256 deletions(-) delete mode 100644 resources/img/pause.svg delete mode 100644 resources/img/play.svg create mode 100644 src/interpreter/athena_obj_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 3e8ced1..8632a2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,12 +510,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bindgen" version = "0.70.1" @@ -835,12 +829,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "colorchoice" version = "1.0.2" @@ -970,7 +958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" dependencies = [ "bitflags 2.6.0", - "fontdb 0.16.2", + "fontdb", "log", "rangemap", "rayon", @@ -1157,12 +1145,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" -[[package]] -name = "data-url" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" - [[package]] name = "dconf_rs" version = "0.3.0" @@ -1519,12 +1501,6 @@ dependencies = [ "miniz_oxide 0.7.2", ] -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - [[package]] name = "float_next_after" version = "1.0.0" @@ -1569,20 +1545,6 @@ dependencies = [ "ttf-parser 0.20.0", ] -[[package]] -name = "fontdb" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2 0.9.5", - "slotmap", - "tinyvec", - "ttf-parser 0.21.1", -] - [[package]] name = "foreign-types" version = "0.5.0" @@ -1774,16 +1736,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gimli" version = "0.28.1" @@ -2165,9 +2117,8 @@ dependencies = [ "bytemuck", "cosmic-text", "iced_graphics", - "kurbo 0.10.4", + "kurbo", "log", - "resvg", "rustc-hash 2.0.0", "softbuffer", "tiny-skia", @@ -2189,7 +2140,6 @@ dependencies = [ "log", "lyon", "once_cell", - "resvg", "rustc-hash 2.0.0", "thiserror", "wgpu", @@ -2246,12 +2196,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "imagesize" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" - [[package]] name = "indexmap" version = "2.6.0" @@ -2344,12 +2288,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - [[package]] name = "js-sys" version = "0.3.70" @@ -2405,16 +2343,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "kurbo" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" -dependencies = [ - "arrayvec", - "smallvec", -] - [[package]] name = "lalrpop-util" version = "0.20.2" @@ -3529,15 +3457,9 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - [[package]] name = "pin-project" version = "1.1.5" @@ -3901,22 +3823,6 @@ dependencies = [ "syn-ext", ] -[[package]] -name = "resvg" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" -dependencies = [ - "gif", - "jpeg-decoder", - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia", - "usvg", -] - [[package]] name = "rfd" version = "0.14.0" @@ -3940,15 +3846,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rgb" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - [[package]] name = "ringbuf" version = "0.4.7" @@ -4068,7 +3965,7 @@ dependencies = [ "radium", "rand", "rustpython-format", - "siphasher 0.3.11", + "siphasher", "volatile", "widestring", "windows-sys 0.52.0", @@ -4240,7 +4137,7 @@ dependencies = [ "adler32", "ahash 0.8.11", "ascii", - "base64 0.13.1", + "base64", "blake2", "cfg-if", "crc32fast", @@ -4584,27 +4481,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" -[[package]] -name = "simplecss" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" -dependencies = [ - "log", -] - [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "skrifa" version = "0.20.0" @@ -4747,9 +4629,6 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] [[package]] name = "strsim" @@ -4788,16 +4667,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" -[[package]] -name = "svgtypes" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" -dependencies = [ - "kurbo 0.11.0", - "siphasher 1.0.1", -] - [[package]] name = "swash" version = "0.1.18" @@ -5302,12 +5171,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - [[package]] name = "unicode-width" version = "0.1.11" @@ -5360,33 +5223,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "usvg" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" -dependencies = [ - "base64 0.22.1", - "data-url", - "flate2", - "fontdb 0.18.0", - "imagesize", - "kurbo 0.11.0", - "log", - "pico-args", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher 1.0.1", - "strict-num", - "svgtypes", - "tiny-skia-path", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - [[package]] name = "utf8parse" version = "0.2.1" @@ -5661,12 +5497,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - [[package]] name = "wgpu" version = "0.19.4" @@ -6233,12 +6063,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - [[package]] name = "yazi" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 1bc5b4f..4402663 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ ahash = "0.8.11" async-channel = "2.3.1" cpal = "0.15.3" env_logger = "0.11" -iced = { version = "0.13.1", features = ["svg", "tokio", "wgpu"] } -iced_aw = "0.11" -iced_fonts = { version = "0.1.1", features = ["nerd"]} +iced = { version = "0.13.1", features = ["tokio", "wgpu"] } +iced_aw = "0.11" +iced_fonts = { version = "0.1.1", features = ["nerd"] } log = "0.4" midi-player = "0.2.1" quick-xml = "0.31.0" diff --git a/pysrc/athenaCL/libATH/athenaObj.py b/pysrc/athenaCL/libATH/athenaObj.py index e6bcac1..0ed96b6 100755 --- a/pysrc/athenaCL/libATH/athenaObj.py +++ b/pysrc/athenaCL/libATH/athenaObj.py @@ -46,6 +46,8 @@ from athenaCL.libATH import ioTools # needed for bkwdCompat object from athenaCL.libATH import rhythm # needed for timing from athenaCL.libATH import language +from collections import UserDict +import athenaObjExt lang = language.LangObj() # from athenaCL.libATH import SC @@ -282,6 +284,24 @@ def getVisualMethod(self, status="normal"): self.visualMethod = drawer.imageFormats() return self.visualMethod +# triggers callback on the dictionary changes +# expects a callback in the form of function(Vec) +# it sends all the keys to the callback +# used to notify gui for changes in somehting like pathLib. +class WatchedDict(UserDict): + def __init__(self, *args, **kwargs): + self.callback = kwargs.pop('callback', None) + super().__init__(*args, **kwargs) + + def __setitem__(self, key, value): + super().__setitem__(key, value) + if self.callback: + self.callback(list(self.keys())) + + def __delitem__(self, key): + super().__delitem__(key) + if self.callback: + self.callback(list(self.keys())) class AthenaObject(object): """methods for internal processsing of done with command objecst from @@ -326,12 +346,12 @@ def __init__(self, termObj=None): self.tniMode = 0 # true == TnI, false == Tn # PATH DATA # self.activeSetMeasure = 'ASIM' # default - self.pathLib = {} - self.activePath = "" + self.initPathLib() + self.setActivePath("") # TEXTURE DATA self.activeTextureModule = "LineGroove" # name of class - self.textureLib = {} - self.activeTexture = "" # name of + self.initTextureLib() + self.setActiveTexture("") # CLONE DATA self.cloneLib = clone.CloneManager() # added post 1.3 self.midiTempo = 120 # default value, changed with TEmidi, added 1.1 @@ -538,6 +558,20 @@ def __init__(self, termObj=None): self.cmdRef = self.cmdManifest() self.cmdRef.sort() + def initPathLib(self): + self.pathLib = WatchedDict(callback=athenaObjExt.pathLibUpdated) + + def initTextureLib(self): + self.textureLib = WatchedDict(callback=athenaObjExt.textureLibUpdated) + + def setActivePath(self, value): + self.activePath = value + athenaObjExt.activePathSet(value) + + def setActiveTexture(self, value): + self.activeTexture = value + athenaObjExt.activeTextureSet(value) + def prefixCmdGroup(self, prefix): """for a given prefix will return a list of cmds and a list of their names diff --git a/pysrc/athenaCL/libATH/command.py b/pysrc/athenaCL/libATH/command.py index 7a81ad7..7ae1110 100644 --- a/pysrc/athenaCL/libATH/command.py +++ b/pysrc/athenaCL/libATH/command.py @@ -781,7 +781,7 @@ def _piAutoCreate(self, name="auto", psList=[0]): if name not in list(self.ao.pathLib.keys()): self.ao.pathLib[name] = pitchPath.PolyPath(name) self.ao.pathLib[name].autoFill(psList) - self.ao.activePath = name + self.ao.setActivePath(name) # all updates now done w/ load opperations def _piAutoCreateMidiPercussion(self, inst): @@ -1117,11 +1117,11 @@ def _tiRemove(self, name): # args is name of texture self.ao.cloneLib.delete(name) # deletes all clones w/ this text if name == self.ao.activeTexture: if len(list(self.ao.textureLib.keys())) == 0: - self.ao.activeTexture = "" + self.ao.setActiveTexture("") else: # gets a random texture to replace - self.ao.activeTexture = random.choice( + self.ao.setActiveTexture(random.choice( list(self.ao.textureLib.keys()) - ) + )) return "TI %s destroyed.\n" % name else: return None # error @@ -1746,7 +1746,7 @@ def gather(self): def process(self): self.ao.pathLib[self.name] = pitchPath.PolyPath(self.name) self.ao.pathLib[self.name].loadMultisetList(self.dataList) # does updates - self.ao.activePath = self.name + self.ao.setActivePath(self.name) def log(self): if self.gatherStatus and self.processStatus: # if complete @@ -2242,7 +2242,7 @@ def _piCopy(self, srcName, dstName): ) == 1: # sets name attribute self.ao.pathLib[dstName] = self.ao.pathLib[srcName].copy(dstName) - self.ao.activePath = dstName + self.ao.setActivePath(dstName) return "PI %s added to PathInstances.\n" % dstName else: return None @@ -2328,13 +2328,13 @@ def _piRemove(self, name): # args is name of path % (name, self.ao.pathLib[name].refCount) ) if name == self.ao.activePath: - self.ao.activePath = "" + self.ao.setActivePath("") del self.ao.pathLib[name] if name == self.ao.activePath: if len(list(self.ao.pathLib.keys())) == 0: - self.ao.activePath = "" + self.ao.setActivePath("") else: - self.ao.activePath = random.choice(list(self.ao.pathLib.keys())) + self.ao.setActivePath(random.choice(list(self.ao.pathLib.keys()))) return "PI %s destroyed.\n" % name else: return None # error @@ -2430,7 +2430,7 @@ def gather(self): return lang.msgPIbadName def process(self): - self.ao.activePath = self.name + self.ao.setActivePath(self.name) def log(self): if self.gatherStatus and self.processStatus: # if complete @@ -2509,7 +2509,7 @@ def gather(self): def process(self): # may change name here self.newPathName = self._piMove(self.oldPathName, self.newPathName) - self.ao.activePath = self.newPathName + self.ao.setActivePath(self.newPathName) def display(self): return "PI %s moved to PI %s.\n" % (self.oldPathName, self.ao.activePath) @@ -2559,7 +2559,7 @@ def gather(self): def process(self): self.ao.pathLib[self.newName] = self.ao.pathLib[self.oldName].copy(self.newName) self.ao.pathLib[self.newName].retro() - self.ao.activePath = self.newName + self.ao.setActivePath(self.newName) def display(self): return "retrograde PI %s added to PathInstances.\n" % self.ao.activePath @@ -2624,7 +2624,7 @@ def process(self): cut = self.rotZero - 1 # needs to be corrected self.ao.pathLib[self.newName] = self.ao.pathLib[self.oldName].copy(self.newName) self.ao.pathLib[self.newName].rotate(cut) - self.ao.activePath = self.newName + self.ao.setActivePath(self.newName) def display(self): return "rotation PI %s added to PathInstances.\n" % self.newName @@ -2686,7 +2686,7 @@ def gather(self): def process(self): self.ao.pathLib[self.newName] = self.ao.pathLib[self.oldName].copy(self.newName) self.ao.pathLib[self.newName].slice(self.sl) - self.ao.activePath = self.newName + self.ao.setActivePath(self.newName) def display(self): return "slice PI %s added to PathInstances.\n" % self.ao.activePath @@ -3548,7 +3548,7 @@ def process(self): self.auxNo, refresh, ) - self.ao.activeTexture = self.name + self.ao.setActiveTexture(self.name) def log(self): if self.gatherStatus and self.processStatus: # if complete @@ -3606,7 +3606,7 @@ def gather(self): return lang.msgTIbadName def process(self): - self.ao.activeTexture = self.name + self.ao.setActiveTexture(self.name) def log(self): if self.gatherStatus and self.processStatus: # if complete @@ -4401,7 +4401,7 @@ def _tiCopy(self, srcName, copyName): self.ao.textureLib[copyName] = self.ao.textureLib[srcName].copy(copyName) # increment references for this path self.ao.textureLib[copyName].path.refIncr() - self.ao.activeTexture = copyName + self.ao.setActiveTexture(copyName) # copy clones if srcName in self.ao.cloneLib.tNames(): self.ao.cloneLib.tCopy(srcName, copyName) @@ -4523,7 +4523,7 @@ def gather(self): def process(self): self.newTextName = self._tiMove(self.oldTextName, self.newTextName) - self.ao.activeTexture = self.newTextName + self.ao.setActiveTexture(self.newTextName) def log(self): if self.gatherStatus and self.processStatus: # if complete @@ -6983,10 +6983,10 @@ def _aoSaveAthenaData(self): def _aoLoadPathData(self, pathData, replace="replace"): """load a path dictionary into current athenaObject""" - self.ao.activePath = copy.deepcopy(pathData["activePath"]) + self.ao.setActivePath(copy.deepcopy(pathData["activePath"])) # self.ao.activeSetMeasure = copy.deepcopy(pathData['activeSetMeasure']) if replace == "replace": - self.ao.pathLib = {} # reinit path bin + self.ao.initPathLib() else: # a name check must make sure that no paths with same name exist pass # do nothing, and add paths if len(list(pathData["pathLib"].keys())) > 0: # paths exist @@ -7018,8 +7018,8 @@ def _aoLoadTextureData(self, textureData, replace="replace"): self.ao.midiTempo = copy.deepcopy(textureData["midiTempo"]) self.ao.activeTextureModule = copy.deepcopy(textureData["activeTextureModule"]) if replace == "replace": - self.ao.activeTexture = copy.deepcopy(textureData["activeTexture"]) - self.ao.textureLib = {} # reinit path bin + self.ao.setActiveTexture(copy.deepcopy(textureData["activeTexture"])) + self.ao.initTextureLib() if len(list(textureData["textureLib"].keys())) > 0: # textures exist for textureName in list(textureData["textureLib"].keys()): t = textureData["textureLib"][textureName] diff --git a/resources/img/pause.svg b/resources/img/pause.svg deleted file mode 100644 index 5e8e864..0000000 --- a/resources/img/pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/img/play.svg b/resources/img/play.svg deleted file mode 100644 index 82e57ad..0000000 --- a/resources/img/play.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/app.rs b/src/app/app.rs index 4b792a2..100731e 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -3,8 +3,8 @@ use iced::futures::sink::SinkExt; use iced::stream; use iced::widget::{ - button, column, container, container::Style as ContainerStyle, row, scrollable, text, - text::Style as TextStyle, text_input, + button, column, container, container::Style as ContainerStyle, horizontal_space, pick_list, + row, scrollable, text, text::Style as TextStyle, text_input, }; use iced::{time, Element, Font, Subscription, Task}; use iced_aw::widget::number_input; @@ -29,6 +29,10 @@ pub struct State { midi_player_state: GlobalMidiPlayerState, scratch_dir: String, input_id: String, + path_lib: Vec, + texture_lib: Vec, + active_path: String, // not system path, but athenaCL pitch path + active_texture: String, } impl Default for State { @@ -60,6 +64,10 @@ impl Default for State { question: None, scratch_dir: String::new(), input_id: "input".to_owned(), + path_lib: Vec::new(), + texture_lib: Vec::new(), + active_path: String::new(), + active_texture: String::new(), } } } @@ -155,6 +163,18 @@ pub fn update(state: &mut State, message: Message) -> Task { Message::SetTempo(tempo) => { state.midi_player_state.set_tempo(tempo); } + Message::PiSelected(value) => { + interpreter::INTERPRETER_WORKER + .interp_sender + .send_blocking(interpreter::Message::SendCmd(format! {"pio {value}"})) + .expect("cannot send message to the interpreter"); + } + Message::TiSelected(value) => { + interpreter::INTERPRETER_WORKER + .interp_sender + .send_blocking(interpreter::Message::SendCmd(format! {"tio {value}"})) + .expect("cannot send message to the interpreter"); + } Message::InterpreterMessage(msg) => match msg { interpreter::Message::SendCmd(ref cmd) => { state.answer = "".to_owned(); @@ -193,6 +213,18 @@ pub fn update(state: &mut State, message: Message) -> Task { interpreter::Message::ScratchDir(value) => { state.scratch_dir = value; } + interpreter::Message::PathLibUpdated(path_lib) => { + state.path_lib = path_lib; + } + interpreter::Message::TextureLibUpdated(texture_lib) => { + state.texture_lib = texture_lib; + } + interpreter::Message::ActivePathSet(path_name) => { + state.active_path = path_name; + } + interpreter::Message::ActiveTextureSet(texture_name) => { + state.active_texture = texture_name; + } _ => (), }, Message::Answer(question, value) => { @@ -255,7 +287,7 @@ pub fn view(state: &State) -> Element { fn view_top_panel(state: &State) -> Element { row![ - button(text("").font(iced_fonts::NERD_FONT).size(16.0)) + button(text("").font(iced_fonts::NERD_FONT).size(16.0)) .style(button::text) .on_press(Message::SetScratchDir), text(&state.scratch_dir), @@ -320,7 +352,7 @@ fn view_input(state: &State) -> Element { Message::Answer(question.to_owned(), state.answer.clone()), ), None => ( - "type command or 'help'", + "type a command or 'help'", interpreter::Message::SendCmd(state.answer.clone()).into(), ), }; @@ -331,20 +363,20 @@ fn view_input(state: &State) -> Element { .style(normal_style) .on_input(Message::InputChanged) .on_submit(on_submit_msg) - .size(16.0), + .line_height(1.7), ) - .height(40) .into() } fn view_bottom_panel(state: &State) -> Element { row![ - // horizontal_space(), - text("Tempo:"), + view_pici_chooser(state), + horizontal_space(), + text("󰟚").font(iced_fonts::NERD_FONT).size(16), + text("=").size(16), number_input(state.midi_player_state.tempo(), 20..=600, Message::SetTempo,) .step(1) .width(60.0), - text("BPM"), ] .spacing(10.0) .padding([18, 0]) @@ -352,6 +384,34 @@ fn view_bottom_panel(state: &State) -> Element { .into() } +fn view_pici_chooser(state: &State) -> Element { + let pi_selection = if state.active_path.is_empty() { + None + } else { + Some(state.active_path.clone()) + }; + let ti_selection = if state.active_texture.is_empty() { + None + } else { + Some(state.active_texture.clone()) + }; + + row![ + text("Path:"), + pick_list(state.path_lib.as_slice(), pi_selection, Message::PiSelected).placeholder("{pi}"), + text("Texture:"), + pick_list( + state.texture_lib.as_slice(), + ti_selection, + Message::TiSelected + ) + .placeholder("{ti}") + ] + .align_y(iced::Alignment::Center) + .spacing(10) + .into() +} + fn pick_directory(title: &str) -> Option { // let initial_dir = env::current_dir().unwrap_or_default(); FileDialog::new() @@ -376,6 +436,8 @@ pub enum Message { Tick(time::Instant), SetTempo(u16), SetScratchDir, + PiSelected(String), + TiSelected(String), } impl From for Message { diff --git a/src/app/midi_player.rs b/src/app/midi_player.rs index 426709e..a588291 100644 --- a/src/app/midi_player.rs +++ b/src/app/midi_player.rs @@ -6,7 +6,7 @@ use cpal::{ Stream as AudioStream, StreamConfig, }; use iced::{ - widget::{button, row, slider, svg, text}, + widget::{button, row, slider, text}, Element, }; use midi_player::{Player, PlayerController, Settings as PlayerSettings}; @@ -99,11 +99,10 @@ pub(crate) struct State { pub(crate) fn view(state: &State) -> Element { let disabled = !state.path.exists() && !state.is_playing; - let label = svg(if state.is_playing { - "resources/img/pause.svg" - } else { - "resources/img/play.svg" - }); + let label = text(if state.is_playing { "" } else { "" }) + .font(iced_fonts::NERD_FONT) + .align_x(iced::Alignment::Center) + .size(24); let message = if state.is_playing { Message::StopMidi(state.id) } else { @@ -116,7 +115,7 @@ pub(crate) fn view(state: &State) -> Element { } else { button.on_press(message) } - .width(60.0), + .width(50), slider(0.0..=1.0, state.position, |v| { Message::ChangePlayingPosition(state.id, v) }) diff --git a/src/interpreter/athena_obj_ext.rs b/src/interpreter/athena_obj_ext.rs new file mode 100644 index 0000000..93142c9 --- /dev/null +++ b/src/interpreter/athena_obj_ext.rs @@ -0,0 +1,61 @@ +//! Extensions to the athenacl's dialog module. +//! +//! The dialog module is responsible for communication between the user and athena interpreter. +//! Also, it implements communication between athena interpreter and GUI by sending messages to the +//! [`InterpreterWorker`](crate::interpreter::InterpreterWorker). + +use rustpython_vm::{pymodule, VirtualMachine}; + +use crate::interpreter; + +pub(crate) fn make_module(vm: &mut VirtualMachine) { + vm.add_native_module("athenaObjExt", Box::new(_inner::make_module)); +} + +#[pymodule] +pub(super) mod _inner { + use std::str; + + use super::*; + use rustpython_vm::PyResult; + + #[pyfunction(name = "pathLibUpdated")] + pub(crate) fn path_lib_updated(path_lib: Vec) -> PyResult<()> { + interpreter::INTERPRETER_WORKER + .gui_sender + .send_blocking(interpreter::Message::PathLibUpdated(path_lib)) + .expect("cannot send message via channel"); + + Ok(()) + } + + #[pyfunction(name = "textureLibUpdated")] + pub(crate) fn texture_lib_updated(texture_lib: Vec) -> PyResult<()> { + interpreter::INTERPRETER_WORKER + .gui_sender + .send_blocking(interpreter::Message::TextureLibUpdated(texture_lib)) + .expect("cannot send message via channel"); + + Ok(()) + } + + #[pyfunction(name = "activePathSet")] + pub(crate) fn active_path_set(path: String) -> PyResult<()> { + interpreter::INTERPRETER_WORKER + .gui_sender + .send_blocking(interpreter::Message::ActivePathSet(path)) + .expect("cannot send message via channel"); + + Ok(()) + } + + #[pyfunction(name = "activeTextureSet")] + pub(crate) fn active_texture_set(path: String) -> PyResult<()> { + interpreter::INTERPRETER_WORKER + .gui_sender + .send_blocking(interpreter::Message::ActiveTextureSet(path)) + .expect("cannot send message via channel"); + + Ok(()) + } +} diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index 0806a49..9c4bf67 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -3,16 +3,15 @@ use std::sync::LazyLock; use std::thread; +use super::{dialog_ext, xml_tools_ext, athena_obj_ext}; use async_channel::{unbounded, Receiver, Sender}; use rustpython_vm as vm; use thiserror::Error; use vm::{ - builtins::{PyBaseExceptionRef, PyInt, PyStr, PyTuple}, + builtins::{PyBaseExceptionRef, PyInt, PyList, PyStr, PyTuple}, Interpreter as PyInterpreter, PyObjectRef, PyResult, VirtualMachine, }; -use super::{xml_tools_ext, dialog_ext}; - /// Global interpreter representation. pub static INTERPRETER_WORKER: LazyLock = LazyLock::new(InterpreterWorker::run); pub(crate) type InterpreterResult = Result; @@ -94,6 +93,11 @@ pub enum Message { GetScratchDir, /// The result of `Self::GetScratchDir`. ScratchDir(String), + PathLibUpdated(Vec), + TextureLibUpdated(Vec), + // Not system file path, but athenaCL pitch path + ActivePathSet(String), + ActiveTextureSet(String) } impl From for Message { @@ -152,34 +156,32 @@ interp"# } fn run_cmd(&self, cmd: &str) -> InterpreterResult { - self.py_interpreter - .enter(|vm| -> InterpreterResult { - let result = vm - .call_method(&self.ath_interpreter, "cmd", (cmd.to_string(),)) - .try_py()?; - let (is_ok, msg) = extract_result_tuple(vm, result).try_py()?; - - if is_ok { - Ok(msg) - } else { - Err(Error::Command(cmd.to_owned(), msg)) - } - }) + self.py_interpreter.enter(|vm| -> _ { + let result = vm + .call_method(&self.ath_interpreter, "cmd", (cmd.to_string(),)) + .try_py()?; + let (is_ok, msg) = extract_result_tuple(vm, result).try_py()?; + + if is_ok { + Ok(msg) + } else { + Err(Error::Command(cmd.to_owned(), msg)) + } + }) } fn scratch_dir(&self) -> InterpreterResult { - self.py_interpreter - .enter(|vm| -> InterpreterResult { - let external = vm - .get_attribute_opt(self.ath_object.clone(), "external") - .try_py()? - .expect("external attribute is always available on AthenaObject"); - let result = vm - .call_method(&external, "getPref", ("athena", "fpScratchDir")) - .try_py()?; - - extract_string(vm, result).try_py() - }) + self.py_interpreter.enter(|vm| -> _ { + let external = vm + .get_attribute_opt(self.ath_object.clone(), "external") + .try_py()? + .expect("external attribute is always available on AthenaObject"); + let result = vm + .call_method(&external, "getPref", ("athena", "fpScratchDir")) + .try_py()?; + + extract_string(vm, result).try_py() + }) } } @@ -193,6 +195,7 @@ pub fn init_py_interpreter() -> PyInterpreter { vm.add_frozen(vm::py_freeze!(dir = "pysrc")); xml_tools_ext::make_module(vm); dialog_ext::make_module(vm); + athena_obj_ext::make_module(vm); }) } @@ -227,6 +230,19 @@ fn extract_result_tuple(vm: &VirtualMachine, result: PyObjectRef) -> PyResult<(b } } +#[allow(dead_code)] +fn extract_vec_string(vm: &VirtualMachine, result: PyObjectRef) -> PyResult> { + result + .payload::() + .ok_or_else(|| vm.new_type_error("Expected a list".to_owned())) + .map(|list| { + list.borrow_vec() + .iter() + .map(|v| extract_string(vm, v.to_owned())) + .collect::>>() + })? +} + fn extract_string(vm: &VirtualMachine, result: PyObjectRef) -> PyResult { result .payload::() diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 4ddd9be..2ed104b 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -2,5 +2,6 @@ pub use interpreter::*; mod dialog_ext; +mod athena_obj_ext; mod xml_tools_ext; mod interpreter;