Layouts dinâmicos com Spec2


View| Edit


Postado por Sebastian Jordan-Montaño October 8, 2021 Publicado em Spectools Tags: GUISpecUI.

Como você já deve saber, o Spec2 é a nova versão da estrutura da interface do usuário: Spec. O Spec2 não é apenas uma nova versão, mas uma reescrita e um redesenho completo do Spec1. Ao contrário do Spec1, no Spec2 todos os layouts são dinâmicos. Isso significa que você pode alterar rapidamente os elementos exibidos. É uma melhoria radical em relação ao Spec1, em que a maior parte do layout era estática e a criação de widgets dinâmicos era complicada.

Nesta postagem, mostraremos que os presenters podem ser compostos dinamicamente usando layouts. Mostraremos uma pequena seção interativa. Em seguida, criaremos um pequeno editor de código com aspectos dinâmicos. Observe que, nesta postagem, usaremos simplesmente Spec para nos referirmos ao Spec2 quando não precisarmos enfatizar a diferença.

Layouts tão simples quanto objetos

Criar aplicativos dinâmicos usando o Spec é simples. De fato, qualquer layout no Spec é dinâmico e pode ser composto. Por exemplo, deixe-me mostrar o seguinte trecho de código:

"Instantiate a new presenter"`

presenter := SpPresenter new.

"Optionally, define an application for the presenter"

presenter application: SpApplication new.

Há três layouts principais no Spec: SpPanedLayout, SpBoxLayout e SpGridLayout. Para este presenter, usaremos o SpPanedLayout, que pode receber dois presenters (ou layouts) e os coloca em uma metade da janela.

presenter layout: SpPanedLayout newTopToBottom.

presenter "openWithSpec" open.

N.T.: Comentei o nome original openWithSpec, que está obsoleto, e coloquei open no lugar para que o código funcione nas versões mais recentes do Spec2.

Obviamente, veremos uma janela vazia porque não colocamos nada no layout.

Screenshot 2024-05-12 at 12.00.43.png

Agora, sem fechar a janela, podemos editar dinamicamente o layout do presenter principal. Adicionaremos um presenter de botão executando as seguintes linhas:

presenter layout add: (
	button1 := presenter newButton).

button1 label: 'I am a button'.

Screenshot 2024-05-12 at 12.05.31.png

Agora, podemos adicionar outro botão. Não há necessidade de fechar e reabrir a janela, tudo é atualizado dinamicamente e sem a necessidade de reconstruir a janela. Como instanciamos o layout com newTopToBottom, os presenters serão alinhados verticalmente.

presenter layout add: (button2 := presenter newButton).

button2 label: 'I am another button'.

Screenshot 2024-05-12 at 12.08.48.png

Agora, podemos colocar um ícone para o primeiro botão:

   button1 icon: (button1 iconNamed: #smallDoIt).

Screenshot 2024-05-12 at 12.12.13.png

Ou podemos excluir um dos botões do layout:

presenter layout remove: button2.

Screenshot 2024-05-12 at 12.13.57.png

O que devemos ver aqui é que todas as alterações acontecem simplesmente criando uma nova instância de um determinado layout e enviando mensagens a ele. Isso significa que os programas podem criar uma lógica simplesmente complexa do comportamento dinâmico de um widget.

Criando um pequeno Smalltalk browser dinâmico

Agora, com todo esse conhecimento, vamos criar uma nova versão mini do System Browser. Queremos ter

Vamos começar. Portanto, primeiro precisamos criar uma subclasse de SpPresenter, chamada MyMiniBrowserPresenter.

SpPresenter subclass: #MyMiniBrowserPresenter
    instanceVariableNames: 
	    'treeClasses button codeShower
	     methodsFilteringList'
    classVariableNames: ''
    package: 'MiniBrowser'

Agora, precisamos sobrescrever o método initializePresenters, no qual inicializaremos os presenters e o layout do nosso minibrowser.

Primeiro, vamos instanciar o presenter de árvore. Queremos que o presenter da árvore mostre todas as classes que estão presentes na imagem do Pharo. Sabemos que todas as subclasses (quase) herdam de Object. Portanto, essa será a única raiz da árvore. Para obter as subclasses de uma classe, podemos enviar a mensagem subclasses, que é o que precisamos para obter os filhos de um nó. Queremos que cada um dos nós (classes) tenha um ícone bonito. Podemos obter o ícone de uma classe com a mensagem systemIcon. Por fim, queremos "ativar" o presenter com apenas um clique em vez de dois. O código será:

MyMiniBrowserPresenter >> initializePresenters`

    treeClasses := self newTree.

    treeClasses

       activateOnSingleClick;

       roots: "Object asOrderedCollection" { Object };

       children: [ :each | each subclasses ];

       displayIcon: [ :each | each systemIcon ].

N.T.: Comentei Object asOrderedCollection, que está obsoleto, colocando no lugar { Object } para o mesmo efeito.

Para os métodos, queremos ter uma lista de filtragem. Ou seja, uma lista na qual possamos pesquisar elementos. Além disso, queremos exibir apenas o seletor do método para o usuário e classificá-los de forma ascendente.

methodsFilteringList := self newFilteringList.

methodsFilteringList display: [ :method | 
	method selector ].

methodsFilteringList listPresenter
    sortingBlock: [ :method | 
	    method selector ] ascending.

Dissemos que, inicialmente, o código estará no modo "Read-Only" (somente leitura). Portanto, o rótulo do botão será "Edit" (Editar), o que significa que, se clicarmos no botão, mudaremos para o modo de edição. Também queremos ter um ícone bonito.

button := self newButton.

button

   label: 'Edit';

   icon: (self iconNamed: #smallConfiguration).

Como o comportamento inicial será o modo somente leitura, o code shower será apenas um presenter de texto que não é editável.

codeShower := self newText.

codeShower beNotEditable.

E, por fim, queremos inicializar o layout do nosso presenter.

self initializeLayout

Aqui está o código completo do método:

MyMiniBrowserPresenter >> initializePresenters

    treeClasses := self newTree.

    treeClasses

        activateOnSingleClick;

        roots: { Object };

        children: [ :each | each subclasses ];

        displayIcon: [ :each | each systemIcon ].

    methodsFilteringList := self newFilteringList.

    methodsFilteringList display: [ :method | 
	    method selector ].

    methodsFilteringList listPresenter
        sortingBlock: [ :method | 
	        method selector ] ascending

    button := self newButton.

    button

        label: 'Edit';

        icon: (self iconNamed: #smallConfiguration).

    codeShower := self newText.

    codeShower beNotEditable.

    self initializeLayout

N.T.: Para mostrar as classes ordenadas por nome use trecho de código:

children: [ :each | 
	each subclasses sorted: [ :a :b | 
		a name < b name ] ];

no lugar de

children: [ :each | each subclasses ];

Posicionando elementos visualmente

Queremos que na parte superior do layout as classes e os métodos sejam mostrados de forma horizontal, como no System Browser (também conhecido como Calypso). Portanto, o que faremos é criar outro layout da esquerda para a direita, com um espaçamento de 10 pixels, as classes e os métodos.

Em seguida, adicionaremos esse layout ao nosso layout principal. O layout principal será um layout de cima para baixo. Depois, queremos o code shower e, em seguida, o botão. Não queremos que o código se expanda e também queremos uma separação de 5 pixels para esse layout.

MyMiniBrowserPresenter >> initializeLayout

    | classesAndMethodsLayout |

    classesAndMethodsLayout := SpBoxLayout 
	    newLeftToRight.

    classesAndMethodsLayout

        spacing: 10;

        add: treeClasses;

        add: methodsFilteringList.

    self layout: (SpBoxLayout newTopToBottom

        spacing: 5;

        add: classesAndMethodsLayout;

        add: codeShower;

        add: button expand: false;

        yourself)

Até aqui, tudo bem... mas não adicionamos nenhum comportamento aos presenters. Para fazer isso, podemos fazê-lo no método initializePresenters ou substituir o método connectPresenters. Para separar claramente a intenção dos métodos, preferimos substituir `connectPresenters.

Conectando o fluxo

Quando clicamos em uma classe da árvore, queremos atualizar os itens da lista de métodos com os métodos da classe selecionada. Quando clicamos em um método, queremos atualizar o texto do code shower com o código-fonte do método.

MyMiniBrowserPresenter >> connectPresenters

    treeClasses whenActivatedDo: [ :selection |

        methodsFilteringList items: selection 
	        selectedItem methods ].

    methodsFilteringList listPresenter

        whenSelectedDo: [ :selectedMethod |

            codeShower text: selectedMethod ast 
	            formattedCode ].

    button action: [ self buttonAction ]

Quando clicamos no botão, queremos várias coisas. Por isso, é melhor criar um método separado. Primeiro, queremos alterar o rótulo do botão para alternar entre "Edit" (Editar) e "Read-Only" (Somente leitura). Em seguida, queremos alterar o presenter do chuveiro de código. Se o minibrowser estiver no modo somente leitura, queremos ter um presenter de texto que não seja editável. E se o Mini Browser estiver no modo de edição, queremos ter um presenter de código que destaque o código e mostre o número de linhas de código. Mas o code shower sempre terá o mesmo texto (o código dos métodos).

MyMiniBrowserPresenter >> buttonAction

    | newShower |

    button label = 'Edit'

        ifTrue: [

            button label: 'Read only'.

            newShower := self newCode ]

        ifFalse: [

            button label: 'Edit'.

            newShower := self newText beNotEditable ].

    newShower text: methodsFilteringList selectedItem 
	    ast formattedCode.

    self layout replace: codeShower with: newShower.

    codeShower := newShower

As a last detail, because we love details, we do not want the “Untitled window” as the window title and also we want a default extent. We override initializeWindow:method.Como último detalhe, porque adoramos detalhes, não queremos a "Untitled window" como título da janela e também queremos uma extensão padrão. Para isso, substituímos (override) o método initializeWindow:.

MyMiniBrowserPresenter >> initializeWindow: 
	aWindowPresenter

    aWindowPresenter

        title: 'My Mini Browser';

        initialExtent: 750 @ 650

Pronto! Temos uma nova versão mínima do System Browser. Se executarmos o script abaixo obtemos a interface do mini browser.

MyMiniBrowserPresenter new "openWithSpec" open.

N.T.: Comentei o nome original openWithSpec, que está obsoleto, e coloquei open no lugar para que o código funcione nas versões mais recentes do Spec2.

Screenshot 2024-05-12 at 15.10.54.png

Screenshot 2024-05-12 at 15.11.28.png

Com o Spec, podemos criar desde aplicativos simples até aplicativos muito sofisticados. As propriedades dinâmicas são simplesmente fantásticas. O Spec tem muitos presenters que estão prontos para serem usados. Comece a se aprofundar no código para ver quais presenters estão disponíveis, qual é a API deles e comece a experimentar e brincar! Os layouts podem ser configurados de várias maneiras, portanto, dê uma olhada em suas classes e nos exemplos disponíveis.

Sebastian Jordan-Montano


Pharo MOC

Translated MOC

Published MOC

Done MOC

Shared MOC