O Darktable tem sua própria biblioteca de widgets de interface, para controles deslizantes e caixas de combinação (também conhecidas como menus suspensos ou caixas de seleção), chamada Bauhaus (no código-fonte, fica em src/bauhaus/bauhaus.c). Embora usem o Gtk como backend, os Bauhaus são objetos personalizados. E, como muitas coisas no Darktable, personalizado é sinônimo de podre.
Em 2022, notei redesenhos parasitas e travamentos ao usá-los, resultando em uma experiência de usuário frustrante: o redesenho do widget parecia esperar a conclusão dos recálculos do pipeline, o que significava que os usuários não tinham certeza se a mudança de valor havia sido registrada, o que podia levá-los a tentar novamente, iniciando outro ciclo de recálculo custoso e efetivamente congelando o computador por vários minutos muito frustrantes de recálculos intermediários e inúteis do pipeline.
Acontece que ferramentas de análise estática de código descobrem que a biblioteca Bauhaus também é o 4º arquivo de código-fonte mais complexo de todo o software Darktable em termos de complexidade ciclomática, com uma pontuação de 735 e uma dívida técnica estimada em 1 dia e 7 horas. Se você não é programador, a complexidade ciclomática mede o número de caminhos diferentes que o código pode seguir, e valores altos o tornam não apenas mais difícil de entender (portanto de depurar), mas também mais propenso a casos extremos, bugs e problemas estranhos dependentes de contexto. A complexidade ciclomática é uma métrica indireta da probabilidade de esse código explodir na sua cara quando você menos esperar, algo a se levar em conta quando os “desenvolvedores” mais prolíficos da sua equipe são um professor primário, um pediatra e um consultor bancário.
O que é particularmente frustrante é que eu já havia trabalhado para simplificar este arquivo, lá em 2019. 3 anos depois, era como se eu não tivesse feito nada, graças ao feature creep e ao suporte a MIDI/gamepad. Quando os redesenhos parasitas apareceram, fiquei com um código espaguete ininteligível que eu era realmente incapaz de consertar. Uma primeira tentativa de corrigir as coisas erradas levou a um beco sem saída, lá em agosto de 2022, e me deixou desanimado. Tentar contornar o problema não iria resolver.
Então tive que reescrevê-lo quase completamente, o que não teve graça nenhuma e me tomou uma quantidade absurda de horas (parei de contar nas 3 semanas, em tempo integral, e isso foi para a segunda tentativa, entre agosto e novembro de 2023).
Você sabia que, uma vez que uma caixa de combinação tivesse o foco (seja porque você clicou nela ou lhe deu foco por meio de um atalho de teclado), você podia começar a digitar as primeiras letras do rótulo que queria selecionar e ela selecionaria automaticamente o item mais próximo da lista? Eu também não sabia antes de empreender esta tarefa, porque não está documentado em lugar nenhum. Assim como muitos recursos ocultos ali, adicionados para atender a casos de uso desviantes e marginais, mas complexificando a estrutura do código para todo mundo. (Alerta de spoiler: mantive este recurso em particular, mas removi outros).
Lista de melhorias
- As coordenadas do cursor (nos popups) são calculadas apenas em um único lugar e depois armazenadas. Isso poupa muitos recálculos intermediários, alguns deles inconsistentes porque o código era copiado e colado e duplicado em vez de usar getters e setters . Agora, os deslocamentos e mudanças de coordenadas são tratados por meio de getters e setters unificados, o que significa que qualquer mudança futura precisará ocorrer em apenas um lugar, e o código inteiro usa isso.
- Novos valores (dos controles deslizantes e das caixas de combinação) não são mais despachados para o pixel pipeline durante a rolagem ou o arrastar-e-soltar, mas apenas no final. Isso se baseia em um tempo limite aprendido por máquina que registra o tempo médio necessário para calcular um pipeline completo. A interface esperará de 20 ms a 2,5 s para despachar as mudanças ao pipeline, evitando recalcular a cada passo de rolagem ou arrasto, o que gera cálculos inúteis e redundantes, porém custosos, que só deixam o software travado. Da mesma forma, agora elas são despachadas apenas no evento de botão liberado, em vez dos eventos de botão pressionado e botão liberado (dado que um clique de mouse típico envia ambos os eventos). Isso deve poupar bastante recálculos desnecessários do pipeline.
- Os widgets são redesenhados imediatamente nos eventos do usuário, antes de os novos valores serem despachados ao pixel pipeline. Isso garante um retorno imediato ao usuário, mesmo que o resultado real em pixels possa vir mais tarde, e limita a frustração em computadores lentos.
- Não despachar eventos de valor-alterado se os widgets receberam interação do usuário, mas seu valor não mudou de fato.
- Capturar cliques no chevron (seta para a direita) das caixas de combinação. Antes, era necessário clicar no rótulo para abrir o menu suspenso da caixa de combinação, o que era supra frustrante se você vinha de um software com uma interface decente. O chevron em si não respondia aos cliques.
- Não rolar os menus suspensos das caixas de combinação. Esse recurso começou como um projeto legal: ter o item atualmente selecionado alinhado com o rótulo. O problema é que o posicionamento do popup suspenso é, no fim das contas, tratado pelo ambiente de área de trabalho; só podemos pedir educadamente que ele faça o que queremos, mas não há garantia de que nossos desejos serão atendidos. Além disso, é possível fazer o popup sair da área visível, nada impede isso. No fim das contas, é frágil e aleatório, melhor ficar com o posicionamento de janela padrão.
Recursos obsoletos
A capacidade de atribuir atalhos de teclado aos controles deslizantes e às caixas de combinação está, por ora, removida. De qualquer forma, os controles deslizantes e as caixas de combinação estão vinculados às teclas de seta e à rolagem do mouse uma vez que recebem o foco1, e ainda é possível atribuir a captura de foco a um atalho de teclado. A lógica atual é, portanto, solicitar o foco por meio de atalhos de teclado e depois editar o valor usando as teclas de seta. As solicitações de foco também tornam o widget automaticamente visível na interface, rolando a barra lateral se necessário.
De qualquer forma, o atual sistema de atalhos terá que ser inteiramente substituído por aceleradores nativos do Gtk, que já são usados no menu global (e eram usados no Darktable antes de 2021). Atualmente, temos os dois sistemas, um dos quais é uma monstruosidade de complexidade e provavelmente responsável por lentidões. KISS.
Ressalvas
A caixa de combinação “formato” no módulo de exportação não inicializa seu valor corretamente. Este é um widget Bauhaus não padrão que exige cuidado extra. Por enquanto, você precisará atualizar a opção de armazenamento ou recarregar uma predefinição.
Sempre que você remove a tinta velha e descascada que sustentava as paredes enferrujadas, você corre o risco de causar danos colaterais. Tenha cuidado com o Ansel ao usar o AppImage e o Win EXE marcados como Ansel-57ed58d desta noite e relate qualquer coisa estranha.
Downloads
Detalhes para geeks
As mudanças atuais reduziram:
- a complexidade ciclomática de 735 para 494
- a complexidade cognitiva de 796 para 432
- a dívida técnica de 1 dia e 7 horas para 4 horas e 50 min.
Se você não é programador, essas métricas significam apenas que o código será mais fácil e menos demorado de manter no futuro, e provavelmente menos propenso a bugs.
Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.
In GUI programming, a widget has the focus when it is the one recording keyboard events. Text entries are the most obvious example, but Darktable hacked that concept to generalize to pretty much every widget. ↩︎