January 21st, 2010

glider

Декларативное описание GUI

Наткнулся сегодня на интересный дискашен о поиске серебряной пули в области гуестроения. Очень порадовала идея ALM - The Auckland Layout Model, которая заключается в задании лаяута путем описания блоков и формул, задающих их взаимоотношения. Например, ниже приведен код, который нужен для задания весьма сложного лаяута на картинке ниже (картинка и текст взяты из домашней странички ALM). Конечно, задание констрейнтов при помощи ручной сборки AST это ужос, также не радует и императивное создание регионов лаяута, но суть не в этом. Смотрим:
Пример сложной формочки, взятый с домашней странички ALM
Copy Source | Copy HTML
LayoutSpec ls = new LayoutSpec();
XTab x1 = ls.AddXTab();
XTab x2 = ls.AddXTab();
XTab x3 = ls.AddXTab();
YTab y1 = ls.AddYTab();
YTab y2 = ls.AddYTab();
YTab y3 = ls.AddYTab();
YTab y4 = ls.AddYTab();
YTab y5 = ls.AddYTab();
 
ls.AddArea(ls.Left, ls.Top, x1, y1, button1);
ls.AddArea(x1, ls.Top, x2, y1, button2);
ls.AddArea(ls.Left, y1, x1, y2, button3);
ls.AddArea(x1, y1, x2, y2, button4);
ls.AddArea(ls.Left, y2, x1, y3, button5);
ls.AddArea(x1, y2, x2, y3, button6);
 
// give the buttons the same size
ls.AddConstraint(new double[] { 2, -1 }, new Variable[] { x1, x2 },
    OperatorType.EQ,  0);
ls.AddConstraint(new double[] { 2, -1 }, new Variable[] { y1, y2 },
    OperatorType.EQ,  0);
ls.AddConstraint(new double[] { 1, 1, -1 }, new Variable[] { y1, y2, y3 },
    OperatorType.EQ,  0);
 
ls.AddArea(ls.Left, y3, x2, y4, checkedListBox1);
ls.AddArea(x2, ls.Top, ls.Right, y2, textBox1);
ls.AddArea(ls.Left, y4, x3, y5, listView1);
ls.AddArea(x3, y2, ls.Right, y5, textBox2);
 
Area richTextBox1Area = ls.AddArea(x2, y2, x3, y4, richTextBox1);
richTextBox1Area.LeftMargin = richTextBox1Area.TopMargin
    = richTextBox1Area.RightMargin = richTextBox1Area.BottomMargin = 10;
 
Area label1Area = ls.AddArea(ls.Left, y5, x2, ls.Bottom, label1);
label1Area.HAlignment = ALM.HorizontalAlignment.LEFT;
label1Area.TopMargin = label1Area.BottomMargin = 4;
 
Area label2Area = ls.AddArea(x2, y5, x3, ls.Bottom, label2);
label2Area.HAlignment = ALM.HorizontalAlignment.CENTER;
label2Area.TopMargin = label2Area.BottomMargin = 4;
 
Area label3Area = ls.AddArea(x3, y5, ls.Right, ls.Bottom, label3);
label3Area.HAlignment = ALM.HorizontalAlignment.RIGHT;
label3Area.TopMargin = label3Area.BottomMargin = 4;


Upd. Читаю один из доков, слинкованных на домашней странице проекта и, доложусь, весьма прикольно. Во-первых, необязательно задавать порядок блоков лаяута - его выводит система из констрейнтов (например, зная то, что лейбл находится левее текстбокса, а таблица находится справа от текстбокса, система выполняет топологическую сортировку и выводит порядок).

Во-вторых, констрейнты на упорядоченность и размеры блоков могут быть мягкими - т.е. каждому ограничению задается пенальти (по умолчанию бесконечный) и в случаях неразрешимости системы ограничений (например, если не хватает разрешения экрана) система не падает, а находит лаяут с минимальным суммарным пенальти. Интересно, умеет ли ALM выводить минимальный требуемый размер экрана для выполнения всех констрейнтов без нарушений. Читаю дальше...

Upd2. Ой, красота! Частным случаем вышеописанной фичи являются пенальти на расширение/сужение. Так, можно сделать так, что при офигенном разворачивании окошка в первую очередь будут расширяться области, которым это важно, а при сильном уменьшении рабочей области в первую очередь под раздачу пойдут наименее важные блоки. Чтобы это реализовать, ALM поддерживает различные коэффициенты сужения и расширения. Например, важный элемент управления может охотно поедать излишнее экранное пространство, имея маленький пенальти расширения, но одновременно неохотно уступать экранное пространство, имея высокий пенальти сужения.