Clojure:如何设计桌面UI

11

我正在尝试设计一个桌面UI,用于原理图、布局和绘画等方面。只是想从实际的软件设计师那里获得高级别的建议。

假设存在一个内存中的 "数据库"(包含所有用户数据的Clojure映射,以及可能包含应用程序首选项等的另一个映射),我正在研究如何在这些上执行模型-视图-控制器的操作,其中数据可以由以下任何一个或多个进行渲染 并由 修改:

  1. 显示单个参数(例如盒子宽度)的独立文本字段。
  2. 显示所选对象的多个参数(例如盒子宽度、高度、颜色、复选框等)的 "检查员" 类型视图。
  3. 显示多个对象的多个参数的表格/电子表格类型视图,可能是整个数据库。
  4. 整个东西的图形渲染,例如原理图和布局视图。

修改这些任何一个应立即出现在每个其他活动视图中,无论是文本还是图形,而不是在单击 "确定" 后...因此,不允许使用模态框。如果由于某种原因,表格视图、检查员视图和图形渲染都在视图中,那么在图形上拖动框的角应立即显示在文本等中。

所讨论的平台是JavaFX,但我想要UI和其他所有内容之间的清晰分离,因此我希望避免按照JFX意义上的绑定,因为那会将我的设计数据与JFX属性紧密地联系起来,增加了模型的粒度,并强制我在处理数据时不得不使用标准的Clojure函数,或者大量涉及整个 getValue/setValue 世界。

我仍然假设至少存在一些状态性/可变性,并使用内置的Clojure功能,例如在原子/变量/引用上添加监视器并让运行时信号依赖函数。

特定于平台的交互将与实际的UI紧密结合,例如具体化 ActionListener, 处理ObservableValue 等,并尝试最小化依赖诸如JavaFX Property这样的实际应用程序数据. 我不考虑使用FRP来实现这一点.

我不介意扩展JFX界面或创建自己的协议来使用应用程序特定的defrecords,但我希望应用程序数据保持为纯Clojure数据,不受平台污染。

问题是如何设置所有内容,以最大程度地遵守不可变模型。我看到几个选项:

  1. 细粒度:每个参数值/原语(例如Long、Double、Boolean或String)都是一个atom,每个可以修改值的视图“达到”数据库中需要的范围以更改值。这可能会很糟糕,因为可能会有数千个单独的值(例如手绘曲线上的点),并且需要大量的(deref...)垃圾。我认为这是JFX想要做的方式,使用巨大的属性数组作为叶节点等,这感觉很臃肿。采用这种方法似乎与只在Java/C++中编码不相上下。
  2. 中等粒度:数据库中的每个对象/记录都是Clojure映射的一个atom。当其中任何一个值更改时,整个映射会被替换。要处理的原子总数较少,例如各种东西的长数组可以直接使用数字表示。但是,当数据库中某些对象需要比其他对象更深层嵌套时,这会变得复杂。
  3. 粗粒度:只有一个原子:数据库。任何时候更改任何内容,整个数据库都会被替换,每个视图都需要重新呈现其特定部分。这有点像用锤子去敲苍蝇,而且一个天真的实现将要求所有内容始终重新呈现。但我仍然认为这是最好的折衷方案,因为任何基元都有清晰的从根节点到访问路径,无论是在基元级别还是在记录级别上访问。

我还需要一个数据模板可以被多次实例化的能力。例如,如果用户更改了在多个地方使用的符号或形状,则单个编辑将应用于所有位置。我认为这也需要某种“指针”行为。我认为我可以将原子存储到模型中,然后根据需要进行实例化,并且它可以在上述任何颗粒模型中工作。

还有其他方法吗?在函数语言中尝试像GUI编辑器一样的工具只是愚蠢的吗? 谢谢


由于您的GUI工具包选择了JavaFX,也许学习SceneBuilderKit(一个用于图形化编辑JavaFX场景的库框架)及其source code 可能会对您有所帮助。它的一些功能与您描述的类似,但也有所不同。但它绝对不是基于Clojure类型的函数模型。 - jewelsea
是的,我在使用JavaFX。在工作、堵车和尖叫的婴儿之间偶尔敲击键盘数月后,我终于成功地使匿名/本地的“ChangeListener”更改了一个原子并触发了Clojure watch。非常不错。我还没有看到SceneBuilderKit(尽管我已经玩过他们的SceneBuilder桌面应用程序),但我会去看看。另一个考虑因素是是否允许JFX使用自己的基元拥有整个场景图,还是以标准的“OnPaint”样式在画布上绘制自己的形状,但这超出了此SO问题的范围。 - Sonicsmooth
是的,Sonic,有很多选项。这是一个很好的问题,但很难回答。我猜如果你不使用场景图和一些预先构建的控件,比如[ControlsFX PropertySheet],那么工作量会更大。也许你可以给clojurefx开发者发电子邮件,询问他对你的问题的看法。 - jewelsea
不妨放弃MVC模式,转而采用Flux架构,并使用core.async通道作为事件总线,这只是我的个人意见。 - myguidingstar
谢谢,我正在尽力不要过于依赖“模式”,但我使用这个术语是为了让读者立即知道我想做什么。在最低层次上,用户交互程序有可变状态,并且UI平台有需要注册的回调函数。如何使用Flux、React、core.async、compojure、datomic、node.js...啊...这太多东西了!我还没有跟上Clojurescript的步伐。我只剩下几个神经元了,所以我正在尽量减少负荷。 - Sonicsmooth
1个回答

5

我认为使用函数式语言来做GUI编辑器类工具并不愚蠢。但是我不能断言我有你问题的答案。这里有一些链接可能会在你的旅程中帮助到你:

  1. Stuart Sierra - 组件 - 恰到好处的结构
  2. Chris Granger - Light Table:解释了Light Table源代码)的结构。
  3. Chris Granger - IDE作为价值:与上面视频相关的博客文章
  4. Conal Elliott - 有形式的函数编程:使用函数响应式编程创建可组合的UI,但他的代码是用Haskell编写的。
  5. Nathan Herzing和Chris Shea - 使用Pedestal、Datomic、Om和core.async帮助选民
  6. David Nolen - 比较文学编程:展示如何使用core.async简化ClojureScript中的UI编程。这里的思路可以用于桌面UI。
  7. Rich Hickey - 系统语言:由Clojure的创造者发表的关于系统编程的精彩演讲。

Erik Meijer有一句很好的关于函数式和命令式代码的名言:

......无论是Haskell、C#、Java、F#、Scala、Python还是PHP,都要考虑有一个与外部世界交互的命令式代码海洋,在其中有着纯净代码的岛屿,你可以用纯净的方式编写函数。 但你必须决定岛屿有多大,海洋有多大。但答案从来不是只有岛屿或只有海洋。优秀的程序员知道如何把握好平衡。


谢谢,这些链接很不错。我之前看过其中一两个,但大部分都是新的对我来说。只需要消化并找到我需要的适当抽象...我想了解何时/如何使用core.async,何时使用promises、futures、agents、原始线程等,而不是仅仅让事情单线程运行,并且如何将低级GUI调用与功能/数据流/异步/FRP/任何抽象相结合。我想这需要多次迭代,并找到添加更多线程有助于改善阻塞/缓慢部分的地方。 - Sonicsmooth
新增了一场演讲,我简直不敢相信我忘记了那场惊人的演讲。 - Rodrigo Taboada

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接