Morphic简介:Self的UI工具包

2021-01-21 02:40:28

在我的前两篇文章中,我谈到了Self语言,以及允许它将对象序列化为文本格式的系统。现在,让我们讨论一下Self的另一个重要部分,它是编程环境和由其创建的UItoolkit:Morphic。

Morphic是为Self构建的抽象UI工具包。它具有X11和Quartz的后端,分别用于GNU / Linux和macOS。它基于变形的概念,在现代UI工具箱中称为小部件。显然,这里的区别是原型方法。毕竟,它完全是自写的,因此采用了原型继承编程范例。所有的morphshave都有一个可以追溯到原始morph的族谱树,它提供了实现自己的morph的基础。

现在,考虑这种情况:对Morphic进行了更新(这种情况是假设的),并且向根变形对象添加了一个新字段,该对象必须传播到所有其他变形,并且需要显示该变形。或更简单的场景;您想要更改所有变形的默认设置,并且希望此更改传播到所有其他变形。但是,您可能还记得我们之前曾讨论过Self中的实例如何工作,尤其是通过复制原型来讨论它们如何工作。那么我们如何轻松地将新的广告位信息从父母那里传播到复制的孩子们呢?答案是抄写。

“复制”是Self称为复制另一个对象中存在的插槽,并在该对象更改时更新这些插槽的功能。您可能还记得在以前的文章中,对象的注释部分中有一些与复制相关的插槽。

在上一篇文章中,我们忽略了这些内容,但是当进行Morphiccopy-downs编程很重要时,我将在这里进行详细介绍。向下复制的工作方式是将“向下复制选择器”作为消息发送给“向下复制父对象”(是一个对象),然后向下复制插槽(“插槽省略”中列出的插槽除外)。只要不忽略更改,就可以传播对父对象的更改。这类似于“子分类”,因为它从父级“继承”了插槽。

向下复制不是Self语言的一部分,而不是其自身的编程环境,其实现完全在注释和镜像系统内完成(我们将在以后的镜像中研究,只知道镜子是Self的反射系统)。 。

手册部分“关于复制”提供了一些有关“复制”的重要信息,但此处没有真正省略的内容。最后,它提到了这一点:

使用抄写的最不方便的方面是,要在道德上等同于创建子类,程序员必须创建两个对象:新的traits对象和新的原型,然后设置新原型的对象注释。也许有一天会有一个按钮来执行此操作,或者可能会出现其他样式的编程。

事实证明,该文档已过时,因为我们确实可以在Selfnow中做到这一点,当我们开始构建自己的变形时,我们将在短时间内利用它。现在,让我们看一下变形实际上是什么。

当我们通过在外壳中键入morph然后然后“获取”来获得根顶点原型时,我们得到了这个对象:

这里的大多数广告位都不是我们关心的问题,因为它们在我们发送消息时由Morphic自动管理(直接与anavaluator或直接与morph交互)。可能有趣的几点是:

hResizing / vResizing:这确定对象根据其父对象或子对象如何调整大小。在“顶点变形”中有三种调整大小的模式,分别是:刚性:对象保持其定义的宽度和高度,并且不会根据其周围环境,父母或孩子的大小而改变大小。

灵活的(或填充空间的):对象会扩大以填充可用空间。如果多个同级对象都具有此属性,则它们都将平均分配宽度/高度。

noStickOuts:如果变形是一个简单的矩形而没有任何突出物,则Morphic会优化图形。

velcroFlag:一种奇怪的名字。如果为真,则无法从该变形中拉出该变形的子变形,也无法将新的子变形拖放到该变形中。

变形的不同“子类”具有其自己的独特位置。值得庆幸的是,Morphic的作者对每个变体的插槽进行了分类,这样,从另一个变体复制下来的一个变体将在一个单独的类别下具有其唯一的插槽,这使得查看属于哪个变体的工作变得容易得多。

每个变体只能出现一次。如果您在大纲板上使用“ Show Morph”来显示已经存在于其他位置的amorph,它将从那里删除自身并将其附加到您的手上(在这方面类似于Javascript DOM)。

就目前而言,其中包含许多子变形的复杂变形很难检查。变形的子变形隐藏在称为rawMorphs的向量中,没有简单的方法来显示变形中可用的子变形的树。幸运的是,自编程环境中有一个工具可以让我们轻松地检查子变形及其父母:核心抽样者。

核心采样器将在其“放大镜”(十字准线)下列出所有变体,从最里面的到最外面。列表中的每个条目均允许您查看其颜色,以及查看和修改其调整大小模式。您还可以在条目上单击鼠标中键以访问变形的属性面板,并获得该变形的轮廓线。

核心采样器对于弄清子变形的原因非常有帮助,因为您可以直接查看其子级并为其打开大纲视图,并在变形运行时对其进行调试。这是我最喜欢的Self之一,因为您可以在系统运行时与系统进行交互,并掌握所有调试信息。

让我们构建一个桌面计算器。它比玩具示例要好(因为它实际上是在做某事),同时又足够简单,以至于帖子不会太长(反而比现在更长)。最终的计算器图形的层次结构如下所示。

CalculatorMorph columnMorph(layout)rowMorph(titleBar)labelMorph(title)uglyTextEditorMorph(editor)rowMorph(buttonRows at:0)buttonMorph('(')buttonMorph(')')buttonMorph( ' CE')buttonMorph(' +')rowMorph(buttonRows at:1)buttonMorph(' 7')buttonMorph(' 8') buttonMorph(' 9')buttonMorph('-')rowMorph(buttonRows at:2)buttonMorph(' 4')buttonMorph(' 5&#39 ;)buttonMorph(' 6')buttonMorph(' *')rowMorph(buttonRows at:3)buttonMorph(' 1')buttonMorph(' 2& #39;)buttonMorph(' 3')buttonMorph(' /')rowMorph(buttonRows at:4)buttonMorph('。')buttonMorph(&# 39; 0')buttonMorph(' BS')buttonMorph(' =')

让我们首先复制frameMorph,它会在其内容周围提供一个矩形框架。从后台菜单中打开带有New shell ...的shell,然后键入frameMorph,然后“获取”以获取frameMorph原型。

之前我们谈到了复制,并说这是我们创建变形的“子类”的方式。现在,我们将下来抄写frameMorph作为我们的CalculatorMorph的基础。右键点击frameMorph的标题,然后点击" Subclass"我(这是我们之前提到的简单复制方法)。当要求提供复制选择器时,将其保留为默认设置,然后单击“确定”。

现在,您可以通过右键单击>“关闭”或使用右上角的X来关闭原始frameMorph。

现在我们有了计算器的基础,就可以显示它了!右键单击我们新创建的变形的标题,然后单击“显示变形”。应该在光标上附加一个小方块,我们可以将其简单地放置到任何空白处。

当然,我们希望它看起来像是Morphic系统的一部分,所以让我们为它选择比蓝色更好的颜色1。在变形轮廓器上打开评估器,然后输入颜色:paint名称:' lightGray' 。然后“做”。

color:是由traits morph提供给我们的消息。您可以在外壳中评估特征变形,以查看可用的功能。请注意,traits morph对象很大,很难找到您要查找的特定对象,但是它与大多数其他标准库对象一样被分类。

现在,我们定义一些我们将要使用的广告位(在一个新类别下,这样以后就不会感到困惑了)。在层次结构的上方,我在括号中为将要引用的变形命名了几个名称。通过从对象菜单中选择“添加类别”来创建类别,然后通过右键单击类别并选择“添加插槽”在其上创建插槽2。

在继续进行操作之前,让我们设置计算器的调整大小模式。默认情况下,它处于灵活模式下,但我们希望它包装其内容。更改大小调整模式有两种方法:

我个人选择了前者,因为它对我来说更快,但是您可以按自己的意愿来做。

现在,让我们为计算器创建布局变形。我们想要垂直放置标题,编辑器和按钮,因此我们将使用columnMorph,它使我们可以将多行放置在彼此下方。复制columnMorph原型并为其设置布局插槽,然后将其添加到我们的计算器变形中。

默认情况下,columnMorph带有丑陋的棕褐色。我们可以通过评估布局颜色将其设置为根本不显示颜色:名为' transparent'的油漆。尽管如此,我们也可以通过评估布局beShrinkWrap 3使其收缩包装内容。

现在,我们创建标题栏。首先,我们将使用label Calculator创建labelMorph,并使其颜色为白色,字体大小为12。然后,为titleBar创建一个rowMorph,并将其颜色设置为蓝色。最后,我们将标题添加到titleBar,并将titleBar添加到布局。

标题:labelMorph copyLabel:' Calculator' FontSpec:(labelMorph fontSpec copySize:12)Color:(绘制名称:' white')。 titleBar:rowMorph复制beFlexible borderWidth:2。 titleBar颜色:油漆名为:' blue' 。 titleBar addMorph:标题。布局addMorph:titleBar。

(当然,您可能不同意我的风格选择。请随意弄乱颜色和字体大小。如果您一路搞砸,请评估removeAllMorphs以清除子变形并从布局部分重新开始。)

现在,添加编辑器变形,这将成为我们的计算器显示。该手册从可用的变形中提供了两个选项作为编辑器:editorMorph是常规文本编辑器(既可以容纳变形又可以容纳文本!),以及uglyTextEditorMorph仅容纳文本但性能更高。我个人更喜欢uglyTextEditorMorph,因为它是评估程序中使用的,而且我认为它看起来更酷。

" offWhite是评估程序用于编辑器的颜色。"编辑器:uglyTextEditorMorph copyString:' 0'样式:(| color =绘画名称:' offWhite'。|)布局addMorphLast:编辑器。

最后,我们将构建按钮。我决定将按钮排列为5行,每行4个按钮。但这是20个按钮!我们是否真的必须手动键入所有这些按钮的初始化?

好,有更好的方法。如果您查看我们的计算器的大纲视图,您会注意到parent *指向插槽对象。如果单击旁边的按钮,它将弹出一个单独的对象。

该对象是所谓的特征对象。如果您自己进行了一些自我环境的探索,您可能已经看到了其中的一些内容。如果它们在全局特征对象中,则轮廓绘制器将通过在左侧边缘上将其着色为紫色来将其标记为特征。但是,正如对traits对象的评论所言,并非所有特性都必须在该对象中,并且许多对象将其特性私密地存储在其原型中(仍可访问,但未注册为“官方”特性)。

在“自我”中,良好的风格是始终将对象的变化部分与不变的部分分开。不变的部分将是代码,而可变的部分将是数据,就像我们的计算器变形中的rawBox属性一样。代码将很少更改,并且当我们需要更改功能时,我们可以创建一个新对象,该对象引用原始的traits对象,并使用父插槽引用该对象;但是,在复制时数据将非常频繁地更改,这将浪费很多时间。每当我们要创建原型副本时,都复制代码。为了解决这个问题,我们将特征对象用于代码,将原型用于初始数据。

当我们“子类化” frameMorph以创建计算器变形时,Selfautomatically为我们创建了一个新的traits对象,该对象引用了traits frameMorph并将其附加到我们新创建的变形中。这使我们能够快速将新方法添加到计算器变形中。因此,我们添加一种便捷方法来为我们快速构建一个按钮。

您需要知道的最后一件事是,Morphic中的按钮需要一个目标(一个对象来执行其操作)和一个脚本(当按下按钮时将执行的代码)。在脚本中,您可以引用target来引用按钮定位的对象。

一切就绪后,让我们在traitobject上添加buildButton:Script::

buildButton:标签脚本:s =(| b |"创建新按钮。" b:ui2Button copyColor :(绘制名为:' lightGray')目标:self。使它变得灵活,设置其标签和脚本。是串联运算符。b beFlexible。b标签:label。b脚本:' target',s。b)

让我们测试一下。输入buildButton:' 1'脚本:' addCharacter:\' 1 \''在ourcalculator morph的评估器上,然后“获取”。按钮轮廓器应附着在我们的手上,我们可以将其放到任何地方。然后,我们可以使用按钮轮廓器上对象菜单中的“显示变形”来显示按钮。

现在,如果单击此按钮,将会收到错误消息,因为我们尚未为计算器变形定义addCharacter:。现在开始吧。我将其放置在“文本操作”类别下。

addCharacter:c =("如果显示为0,而我们没有按。,则将显示内容替换为我们刚才输入的字符,就像计算器一样。"(编辑者contentsString = 0)&amp ;& [c!='。'] ifTrue:[编辑器setText:c]"否则,只需将字符插入光标点。" False:[编辑器文本insert_char :c]。self)

如果现在单击该按钮,您应该看到它以1s填充计算器显示。您刚刚学习了如何连接Morphic小部件!但是,等等,我们还没有完成。我们需要为计算器创建一个按钮网格,就像我在本文前面的层次结构中所显示的那样。为此,我们只需要在buttonRows插槽上定义一个新向量,并用rowMorphs填充其中的每个元素,然后将按钮插入每行。

(您可以通过从变形菜单中选择“关闭”(右键单击)来摆脱刚刚创建的按钮)。

让我们一次迈出一步。首先,让我们通过在评估器中评估以下代码段,用rowMorphs填充buttonRows。

buttonRows:矢量copySize:5。特质整数do:是在Self中完成范围循环的方式,类似于Ruby。 5做:[| :一世 。 r | r:rowMorph复制beFlexible borderWidth:0。 r颜色:油漆名称:'透明' 。 buttonRows位于:i放置:r。布局addMorphLast:r。 ]。 "让布局知道布局已更改。"布局layoutChanged。

我们终于可以添加按钮了。由于每个按钮都是唯一的,因此我们将它们逐个添加:

(buttonRows位于:0)addMorphLast:buildButton:'('脚本:' addCharacter:\'(\''。(buttonRows位于0)addMorphLast :buildButton:')'脚本:' addCharacter:\')\'' 。 (buttonRows位于:0)addMorphLast:buildButton:' CE'脚本:'清除' 。 (buttonRows位于:0)addMorphLast:buildButton:' +'脚本:' addCharacter:\' + \'' 。 (buttonRows位于:1)addMorphLast:buildButton:' 7'脚本:' addCharacter:\' 7 \'' 。 (buttonRows位于:1)addMorphLast:buildButton:' 8'脚本:' addCharacter:\' 8 \'' 。 (buttonRows位于:1)addMorphLast:buildButton:' 9'脚本:' addCharacter:\' 9 \'' 。 (buttonRows位于:1)addMorphLast:buildButton:'-'脚本:' addCharacter:\'-\'' 。 (buttonRows位于:2)addMorphLast:buildButton:' 4'脚本:' addCharacter:\' 4 \'' 。 (buttonRows位于:2)addMorphLast:buildButton:' 5'脚本:' addCharacter:\' 5 \'' 。 (buttonRows位于:2)addMorphLast:buildButton:' 6'脚本:' addCharacter:\' 6 \'' 。 (buttonRows位于:2)addMorphLast:buildButton:' *'脚本:' addCharacter:\' * \'' 。 (buttonRows位于:3)addMorphLast:buildButton:' 1'脚本:' addCharacter:\' 1 \'' 。 (buttonRows位于:3)addMorphLast:buildButton:' 2'脚本:' addCharacter:\' 2 \'' 。 (buttonRows位于:3)addMorphLast:buildButton:' 3'脚本:' addCharacter:\' 3 \'' 。 (buttonRows位于:3)addMorphLast:buildButton:' /'脚本:' addCharacter:\' / \'' 。 (buttonRows位于:4)addMorphLast:buildButton:'。'脚本:' addCharacter:\'。\'' 。 (buttonRows位于:4)addMorphLast:buildButton:' 0'脚本:' addCharacter:\' 0 \'' 。 (buttonRows位于:4)addMorphLast:buildButton:' BS'脚本:'退格' 。 (buttonRows位于:4)addMorphLast:buildButton:' ='脚本:'评估' 。

这些功能大多数都非常简单明了,它们只是向显示器添加字符,但是有一些我们尚未实现的功能,所以现在就在traits对象上进行操作。

每个方法都是一个单独的插槽,因此请确保从对象菜单中创建3个新插槽。

"重置计算器输入。 clear =(编辑器setText:' 0')。 "在光标处删除一个字符。如果显示为空,则将其设置为' 0'。 Backspace =(编辑器文本Backspace。(编辑contentString =' 0')ifTrue:[编辑器setText:' 0']。 "在编辑器显示上求值表达式并写入结果。评估=(编辑setText:编辑文本eval asString)。

评估使用traits字符串eval来评估文本显示的内容作为Self表达式,这很好用。但是,Self不具有运算符优先级(因为运算符只是具有特殊名称的消息),并且如果您将不同的运算符彼此并排放置,则会抱怨,因此您需要使用括号。

我们的计算器终于完成了!通过实践(以及大量的Morphic工具包探索),我花了大约15分钟从头开始构建它。

当然,我们构建了计算器,但是将其构建为“原型”计算器:当用户想要使用计算器时,他们应该复制并使用该副本。为此,我们需要设置更多内容。

当我们复制计算器时,Self将创建所有插槽的副本:这包括子变形和我们的参考插槽。显然,这带来了一个问题:现在存在我们所引用的变形的两个不同副本,插槽的值和嵌入在变形中的子变形。为了解决这个问题,特质使副本词形化以做一些额外的工作。特别是,复制完成后,它将对每个复制的morph调用mapReferencesUsing:将旧的morph引用归结为新的morph。使用

......