一些关于UiKit的问题

受Emacs Application Framework启发,我暑假的时候开始计划一个Emacs的文本UiKit框架。 因为我只是大二学生,没有什么开发经验,开发的时候遇到一些疑问无法解决,希望大家能帮助我,谢谢 :smile:

这个框架

先介绍一下这个框架。基本思路就是模仿cocoa的UI模式,一切皆View,然后用Stack把几个View组合起来。 Stack既可以用来管理View的layout,又可以组合几个固定的View形成新的View。

举个例子:如果我把两个Button和一个Label用一个stack组合起来并把它看作View,一个Stepper就产生了。比如:5 +-(假装+-是Button) 按+会数字增加,-反之。

用Stack管理view就很简单了:把一堆View放到Stack里,Stack安排他们的位置。

每一个页面都是一个Scene,Scene其实就是一个特殊一点的Stack。

每个独立应用是一个app,app定义了一些退出,启动的设置,还有entry Scene啥的。

更多的内容请看README,我今天刚上传GitHub了:GitHub - casouri/uikit: Text based uikit for Emacs

我的问题

  1. 用来表示位置的数据结构:我目前用的是struct,但是不知道一个简单的cons (x . y)会不会更好?

我目前的实现:

(cl-defstruct uikit-pos
 "Represents a position in cavas.
both x and y are zero based.
Note that the length of the unit in y direction is about two times as
the unit in x direction.
For example (20, 10) nearly forms a square."
 (x 0)
 (y 0))
  1. Purcell说我的这两个macro有多次eval的问题,但是我不太明白为什么,不eval的话就没法改变seq啊……

他说的是我在melpa上发的一个包里的macro,但是具体实现是完全一样的: https://github.com/melpa/melpa/pull/5653#issuecomment-417159566

当然他说了改成defsubst,我没搞明白怎么用,网上也没有详细的文档。(Common Lisp也没有……)

(defmacro uikit-append (seq elt)
  ;; TOTEST
  "Append ELT to SEQ destructivly. This is a macro."
  `(if ,seq
       (nconc ,seq (list ,elt))
     (setq ,seq (list ,elt))))

(defmacro uikit-push (elt seq)
  ;; TOTEST
  "Push ELT to SEQ destructivly. This is a macro."
  `(if ,seq
       (push ,elt ,seq)
     (setq ,seq (list ,elt))))
  1. 我设计的框架里消息传递基本靠symbol,比如你创建一个Button叫mybutton。关联按下启动的函数只要(defun mybutton-pressed)就行。 我的问题是这个传递消息的id应该用字符串还是symbol?哪个效率会高一点?因为用symbol的话每次用的时候还要转化成字符串再intern回去。

比如:(intern (format "%s-width" (symbol-name view-id)))

  1. 这个是关于auto layout的问题。auto layout是目前主要困扰我的问题,我之前差了一些simplex method之类的方法,但是感觉有点太overdone了。杀鸡用牛刀。 我目前的设计是这样:

    1. Stack有三种安排sub view的模式:equal spacing,stacking,和portion
    2. equal spacing就是stack的长度在autolayout之前已经知道了,可以是上一级Stack分配的,也可以是开发者手动设置的。然后Stack获取所有 sub view的长度, 也就是说sub view的长度在autolayout的过程中能求出来,用自己的长度减去所有subview的长度,剩下的空余均分,插入到各个sub view之间。
    3. stacking很简单,就是把所有sub view放到一起,中间可以间隔开发者设置的间距,Stack的长度是由sub view决定的,只有auto layout结束了以后才能知道Stack的长度
    4. portion:如字面意思,给每个sub view分配Stack一定比例的长度,和equal spacing一样是需要Stack长度在autolayout之前已知的。

    我不太清楚这个系统怎么样,就起来看实现起来有点麻烦但不算太糟,功能上也应该能满足大部分需求。但是规则可能有点乱,而且容易出BUG?我没啥经验看不出来,希望懂得大佬 提提建议。

  2. 如果大家有闲心看看我的代码,希望你们能给我提提意见,有任何问题请尽管问。如果有对设计感兴趣的欢迎讨论。

谢谢 :smile:

多进程传递用字符串,没有编码和解码负担。

如果要传递复杂结构,把json包到字符串里面

你这个怎么用

还不能用,我发上来是想问几个问题。

你一定想试试的话: 首先pull一下uikit.el。我刚修复了一个bug……

M-x eval-buffer
;; 创建一个button
(setq mybutton (make-instance 'uikit-button :id 'mybutton))
;; 创建一个pos
(setq mypos (make-uikit-pos :x 20 :y 10))
;; 新建一个空buffer
(switch-to-buffer (get-buffer-create "test"))
(uikit-prepare-canvas)
;; 最后把button放上去
(uikit-draw mybutton mypos)

button默认就是”Button“,tooltip是默认的“Click”,点一下会在minibuffer提示你overload点击函数。

pos的:x, :y就是屏幕上的坐标

谢谢。那就是字符串了。我查了查,没有提到Emacs Lisp的string是在堆还是栈上?

多次eval的问题,应该是因为seq会被eval两次,如果seq是个复杂的表达式的时候,有可能就改了某个全局变量

In particular, stack-allocated objects should never be made visible to user Lisp code.

1 个赞
(defsubst uikit-append (seq elt)
  ;; TOTEST
  "Append ELT to SEQ destructivly. This is a macro."
  (if seq
       (nconc seq elt)
     (setf seq elt)))

defun 用法一样,用来定义 in-line 函数。你这里完全没有必要定义 macro

1 个赞

此Stack 非彼Stack ?

他问 string allocate 在 stack 还是 heap 上。

当然我也不知道他为什么忽然会问这个,似乎和问题并无关系。

这个好像只能用在C代码中吧

Currently, cons cells and strings can be allocated this way. 
This is implemented by C macros like AUTO_CONS and AUTO_STRING 
that define a named Lisp_Object with block lifetime. 

These objects are not freed by the garbage collector;
 instead, they have automatic storage duration, i.e.,
 they are allocated like local variables and are automatically 
freed at the end of execution of the C block that defined the object. 
/* Declare NAME as an auto Lisp string if possible, a GC-based one if not.
   Take its value from STR.  STR is not necessarily copied and should
   contain only ASCII characters.  The resulting Lisp string should
   not be modified or made visible to user code.  */

#define AUTO_STRING(name, str)						\
  Lisp_Object name =							\
    (USE_STACK_STRING							\
     ? (make_lisp_ptr							\
	((&(union Aligned_String)					\
	  {{strlen (str), -1, 0, (unsigned char *) verify_ascii (str)}}.s), \
	  Lisp_String))							\
: build_string (verify_ascii (str)))

带seq的宏是特别不好整的, onlisp 和 lambda over 拉面不大 都提到过。。。。

对啊,Emacs Lisp 操作的物件都是堆上分配的

我觉得楼主看不到这一层

onlisp和lambda over lambda是啥? 哦哦你是说let over lambda?

(cl-defmethod send ((obj (eql zz)))
  (print "for zz"))
send

(cl-defmethod send ((obj (eql bb)))
  (list obj 2))
send

(send 'zz)

"for zz"
"for zz"

(send 'bb)
(bb 2)

谢谢!这个很赞!我之前一直在想用eql比对view对象,发现不可以就放弃了。现在发现用id比对也是可以的。

怎么感觉这个是在演示 消息传递模式的面相对象和基于广义函数的面相对象的 区别?

实际上是沒区別的。CLOS 设计的时候参考了 Lisp Machine Lisp 上消息传递模式的 Flavors,而 Flavors 的 send 实現:

;; example (send my-ship :set-x-position 3.0)
(defsubst send (obj &rest args)
  (apply obj args))

Flavors 能做的基本可以直接转成 CLOS。

你不是应该尽量减少与他人的争论及回答别人能够回答的问题吗