PX, Un toolkit X11 sous Linux [C++] - C++ - Programmation
Marsh Posté le 10-09-2013 à 08:50:34
Explication du code
1. libPX.cc: la boucle des évènements
Code :
|
2. px_widget.cc: la fonction de dispatch des évènements
Code :
|
3. px_*.cc: les différentes fonctions de gestion des évènements
Marsh Posté le 10-09-2013 à 08:50:43
Hiérarchie des classes de widgets
En bleu ciel, les classes abstraites
En bleu roi, les classes + ou - implémentées
En orange, les classes non implémentée mais qui possèdent déjà un fichier *.cc
Marsh Posté le 10-09-2013 à 09:17:21
Ceux qui voudront l'utiliser pour eux,
pourrons le faire dans le cadre d'une licence OpenSource (non encore définie)
J'aimerais surtout que des personnes me rapporte des remarques
sur des choses que j'ai clairement mal implémentées
ou que j'aurais pu implémenter plus simplement.
Ce qui m'intéresserais aussi et surtout c'est que vous me disiez
les fonctionnalités que vous voudriez voir dans un toolkit
- Look-n-feel "Blender" ?
- Composant Editeur de texte avancé ?
- avec folding/repli des blocs des codes
- colorisation syntaxique
- autre chose ...
Et la base ...
- Lissage des polices (non implémenté)
Bientôt un lien vers les sources ...
Marsh Posté le 10-09-2013 à 16:36:15
Oué, les toolkits d'interface ... un domaine dans lequel je travaille plus ou moins directement depuis une 15aine d'années. Autant dire que j'ai eu le temps de voir un peu près tout ce qui se faisait sous le soleil: AmigaOS, X11/Xt/Xm (motif), un peu de Java/AWT et Java/Swing, Win32, MFC, wxWidgets, un peu de GTK, un peu de QT, évidemment HTML/JS et .... mon propre toolkit (SIT: simple interface toolkit, basé sur Win32).
Avant de faire des remarques sur le peu que tu as montré, la moindre des choses que tu devrais faire, c'est:
* Un exemple de code "Hello World": une fenêtre, un label "Hello World" + boucle d'événement minimale. Histoire d'avoir une idée de quoi ressemble l'API.
* Un screenshot avec quelques widgets un peu plus avancés + le code source.
Comme je te l'ai dit, j'ai touché à pas mal d'API dans ce domaine et il y a un piège qu'il est extrêmement facile de tomber quand on fait une API d'interface: over-engineering, ou ce que j'aime bien appeler le fardeau cognitif. Le but de toute API est à mon sens est de minimiser ce fardeau, parce qu'il y a des limites à ce que le cerveau peut emmagasiner et on a bien souvent autre chose à foutre que de comprendre l'esprit parfois tordu qui a conduit à certains choix d'API. Dans ce domaine, il y a quelques classiques qui ont foiré cet aspect:
* Win32: je devrais plutôt dire la partie "comctl" (common control) et dans une moindre mesure GDI, parce que le reste est assez banal (mais très stable). Bourré de piège à con, exemple MSDN ultra merdique, documentation pas toujours très claire, API mamouthesque, ultra bas niveau: faut pas s'étonner si une génération entière de programmeur ont maltraité cette API et que Microsoft se retrouve avec toute une clique d'application mal foutue, qui bride toute évolution: quand on a fardeau cognitif aussi élevé, faut pas espérer que tout le monde fasse l'effort de bien maitriser l'API.
* GTK: l'API est assez propre, mais la productivité de ce toolkit est merdicimale. C'est la raison de pourquoi je n'ai pas cherché à m'investir plus dans ce truc. L'API est composé de plusieurs milliers de fonctions au nom abominablement longs et extrêment chiant à s'en souvenir, même avec de l'auto-complétition. Pour un peu, j'avais l'impression de coder en J2EE.
* MFC/wxWidgets: codé à une époque ou le C++ était du C avec des classes, le résultat est que tu te prends dans la figure le fardeau cognitif du C++ mal employé avec le fardeau de l'API. Ça à le mérite de fonctionner, mais absolument pas plaisant à utiliser.
À l'opposé ceux qui ont plutôt bien conçu :
* HTML/JS: pas sûr de faire l'unanimité, mais de mon expérience (8ans) avec l'API DOM + HTML, le fardeau cognitif est relativement faible. L'API est un peu verbeuse (document.createElement(...), document.getElementById(), ...), mais c'est très facile à changer. Le choix des widgets est un peu limité, mais on a toute la liberté du HTML pour combler les lacunes.
* Xm/SIT: dans le contexte de l'époque, c'était pas mal (à l'heure actuelle, ça a plutôt mal vieillit: j'avais revu certains morceaux de code que j'avais écris avec Xm, il y a plus de 10ans, bah, c'était pas super clair). Le noyau dur de l'API était composé de 4 ou 5 fonctions qui te permettais de coder 99% de l'application: la surcharge cognitive était extrêmement faible. Mais il y avait pas mal de trucs moisi: gestion des chaines de caractères (XmStringCreate(), XmStringCreateLocalized(), ...), API verbeuse et limite chiante à regarder (une suite sans fin de XtCreateManagedWidget()), et un choix de widget limité, pour ne pas dire désuet même pour l'époque (XmNoteBook, XmList), licence de merde, etc... SIT (mon toolkit, donc) utilise une API similaire à Xm, mais j'ai viré tout ce que je considérais moisi (chaine de caractère, verbosité, choix de widget pas terrible, ...). Je pense avoir fait un bon boulot, mais ça n'est pas aussi simple qu'HTML/JS. Un exemple de hello world avec SIT:
Code :
|
Ça donne:
Marsh Posté le 11-09-2013 à 11:14:01
Pour l'instant mon hello world s'écrit comme ça
Code :
|
et ressemble à ceci
Bon, il n'y a pas encore de gestion des fontes et la position du texte dans le label
En fait j'ai programmé le composant label hier rapidement, histoire de montrer quelque chose.
Mais en fait je vais voir si je peux pas transformer les new PXWindow() par des méthodes de classe PXWindow::Create()
Sinon le PXObj .. (que je vais renommer en PXApplication) doit s'occuper de tous les écrans d'un display (voir faire du multi-displays)
J'ai pas encore programmé les Callbacks mais je pense les écrire comme ça widget->AssignCallback(pxOnClick, proc)
Sinon, il est pas mal ton toolkit, c'est assez différent du mien, il a l'air sophistiqué.
tu as pensé a le porter en C++ ?
J'attends tes remarques ...
Marsh Posté le 11-09-2013 à 12:26:12
un exemple d'interface plus complète
Code :
|
Marsh Posté le 11-09-2013 à 19:05:18
Mouais, l'avantage d'avoir utilisé une tétrachiée d'API, c'est qu'on eu le temps de séparer ce qui est utile, de ce qui est de la masturbation intellectuelle. Dans tous les cas, je trouve que l'approche objet pure (héritage, polymorphisme, ...) pour ce genre d'API, n'apporte pas grand chose, et dans le cas du C++, je serais tenté de dire que le fardeau cognitif du langage apporte plus de problèmes que ça n'en résouds (note: je parle de la façade, en interne il y aura forcément une approche objet).
Une application de bureau finalisée, ça demande pas mal d'effort et de souci du détail (plus qu'une appli web, je trouve): tu n'as en général franchement pas envie de te battre contre l'API et/ou le langage. Il y a un équilibre pas évident à trouver: permettre une certaine liberté d'expression, sans imposer une surcharge cognitive démentielle. Dans le cas de SIT, j'ai poussé cette logique aussi loin que possible, et à force de virer des trucs inutiles, c'est là qu'on se rends compte que la plupart des concepts du C++ deviennent inutiles aussi.
Par exemple: la gestion de tes menus. Tu as utilisé une approche objet/hiérarchique classique. 7 variables déclarées, pas mal de surcharge liée à l'API (menu->AddItem, menu->AddSeparator, ...): la partie des données propre au menu est noyée dans les appels de méthodes et même à ce prix tu as juste créé le texte et la hiérarchie du menu, rien d'autre. Compare ça, avec une table de structure tout ce qui a de plus basique (inspiré d'AmigaOS):
Code :
|
Et tu sais quoi? Ça couvre tous les cas de figure que j'ai eu à gérer jusqu'à présent et ça simplifie un paquet de trucs:
On peut faire la même remarque pour la création de tes boutons:
Code :
|
Vu comme ça, sans connaître les paramètres de ton constructeur, ce code n'est pas spécialement clair. D'autant que ça va être la merde si tu veux faire évoluer ça:
Tu sais comment simplifier ça ? une interface vararg en C, tout ce qu'il y a de plus basique. C'est l'approche utilisé par Xt et donc Motif et dans une moindre mesure AmigaOS. Hop, ça simplifie:
Cela dit, le défaut d'une interface vararg, c'est qu'il soit préférable que l'attribut encode la taille de l'argument (je ne l'ai pas fait dans SIT, je pense que c'est une erreur) et prier pour l'argument soit effectivement de cette taille. Avec une API 32bits, ça passe plus souvent que ça ne casse parce qu'un paquet de truc fait 4 octets (char [promotion implicite], short, int, long, void *, il y a juste long long et double qui font 8octets). En 64bits, c'est un peu plus casse gueule. Dans SIT, j'ai un peu mitigé le problème en passant par des macros (certains attributs seulement): la macro définit l'attribut et converti (cast) l'argument avec le bon type (en général quand l'argument est de type int64). Rien n'est parfait, mais je pense que c'est un bon compromis entre simplicité, surcharge cognitive pour l'utilisateur et surcharge au niveau du code pour gérer tout ça (notamment la préservation de l'ABI est un plus non négligeable).
Par exemple pour créer ton interface, en SIT, ça donne :
Code :
|
4 fonctions, 1 table de structure (avec les callbacks, il y aurait une fonction de plus + un prototype standardisé pour le callback à connaitre).
Note: SIT_CreateWidgets() utilise encore une autre interface: un mix d'HTML et de vararg. Ça a l'avantage d'être extrêmement compact, on voit relativement bien l'imbrication des widgets et les paramètres qui leur sont transmis. Comme son nom l'indique, elle est basée sur SIT_CreateWidget(). Par exemple pour créer le premier bouton, ça donnerait un truc du gerne :
Code :
|
Marsh Posté le 11-09-2013 à 20:47:15
Juste histoire d'en rajouter encore une couche: ce que j'essaye d'illustrer, c'est ce que j'ai dit au début: toute API se doit de minimiser le fardeau cognitif. J'ai donné mon toolkit en exemple, mais ce n'est pas une parole d'évangile à suivre aveuglément. Si tu arrives à trouver une combinaison de fonctions/structures/classes/templates/incantations vaudoux, qui fait qu'il y a moins de concepts à connaitre, avec une API plus robuste, plus expressive, qui puisse éventuellement s'intégrer à des systèmes existants (mais avec l'API win32, tu vas pleurer), il vaut mieux ne pas copier pas ce que j'ai fait, mais y chercher de l'inspiration.
Parce bon, même si je pense avoir fait un bon boulot avec SIT, c'est loin d'être parfait:
Marsh Posté le 12-09-2013 à 04:16:27
Juste une remarque sur ton toolkit
ce qui surtout gêné c'est les ID de widget, et la fonction SIT_GetById(dialog, "ok" )
je suppose que dans une interface importante, on doit en retrouver énormément.
Je préfère quand même l'encapsulation objet qui offre sécurité et aussi et surtout un appel simple des méthodes spécifiques widget->MethodeDeLaClasse() (en interne et pour l'API)
Je sais pas comment tu fait ça avec les ID !?
Aussi non, c'est clair que la création de mes menus est lourde
je m'en suis aperçu.
Je vais voir ce que je peux faire pour le toolkit (pour les menus je vais utiliser une struct)
Peut être opter pour un mélange entre souplesse de la variabilité des arguments,
mais sans avoir a écrire un mini-parser pour lire la structure balisée de la déclaration de l'interface.
Peut-être pour des prototypes de fonctions de création comme :
Code :
|
avec PXArg une chaîne: "attribute_name=value"
(Faut que je vois si on peut faire ça en C++ pour les constructeurs)
ou encore
Code :
|
Marsh Posté le 12-09-2013 à 20:56:31
Tu devrais essayer d'approcher ton API à niveau beaucoup plus haut: avant de regarder les spécifités techniques que tel ou tel langage peut apporter, demande toi qu'est ce que tu cherches à apporter à l'utilisateur (de ta DLL, donc du programmeur). À ce niveau c'est trop tôt de ce soucier de l'utilisateur final. Dans le cas de SIT, j'avais ces idées en tête:
Tu as l'air de tenir à l'analyse statique du compilateur. Je ne te cacherais pas qu'après avoir travaillé pendant des années avec les technos web, ce n'est pas un point qui m'a semblé important. Il y a deux écoles: celle qui préfère faire un maximum de vérification à la compilation, quitte à avoir une API verbeuse/complexe, et l'autre qui préfére avoir une API relativement générique, qui fait son possible pour honorer les demandes, quitte à ignorer/corriger les erreurs. Les deux approches se valent, mais là encore après toutes ces années à coder avec les technos web, je préfère la seconde (c'est par exemple aussi l'approche utilisé par sqlite (http://www.sqlite.org/different.html#Manifest%20typing)).
Tu vois: avant même d'écrire la moindre ligne code, j'ai déjà fait des choix très structurants: ABI simple = pas de C++ en façade. Là aussi ça aide d'avoir un peu regardé ce qui se fait ailleurs, histoire d'éviter de faire les mêmes erreurs/reprendre les bonnes idées. Tu n'as pas l'air très décidé sur ton architecture globale, alors si je peux te donner un bon conseil, je vais te répéter ce que j'ai dis au début: pense au fardeau cognitif. Chaque classes, fonctions, structures, callbacks, templates, ... devra être documenté un jour ou l'autre (que tu en arrives là ou pas, ce n'est pas le problème, mais si tu ne comptes pas en arriver là, c'est que tu es probablement sur le mauvais chemin). Imagine toi documenter un aspect: y en a t-il pour des pages ou est-ce torché en 10 lignes? C'est facile d'estimer ça lorsqu'on implémente une fonctionnalité en bout de chaine, voir ça au plus haut niveau de l'API, ça demande une réflexion assez poussée. Ça veut dire pas mal de tentatives dont certaines finiront nécessairement à la poubelle.
Marsh Posté le 14-09-2013 à 23:34:01
Très intéressante cette discussion.
Concernant l'histoire d'ajouter de la type safety à l'API, je suis pas certains que ca oblige à avoir une API verbeuse et/ou complexe. (bon en C++ ca va etre delicat de faire un truc clean, quoique possible mais le code de la librairie deviendra extreme en terme de metaprog .. apres y'a toujours la possiblité de faire un pseudo-DSL comme ton SIT_MenuStruct mais typé)
Marsh Posté le 18-09-2013 à 22:50:21
un DSL en C++ qui ferait que la GUI deviennent declarative et non aps un amoncelement d'appel de methode serait pas mal. J'avais tenter un truc dans le genre y a qqs temps.
Marsh Posté le 19-09-2013 à 13:15:43
Ces discussions m'ont bien fait avancé, même si j'ai gardé l'héritage et le polymorphisme. (sans en abuser)
Les 1ers paramètres des fonctions de création seront typés:
Code :
|
tous les autres paramètres seront passés dans une string.
Code :
|
Je programme intensément actuellement, j'espère vous montrer quelque-chose dans 2 à 3 mois.
Marsh Posté le 10-09-2013 à 08:50:21
Bonjour,
J'ai commencé a trifouiller du code X11 en C vers 1995-1996
a une époque ou n'existait que Motif voir Lesstif comme toolkit
pas de GTK, encore moins de Qt
Mais seul, je n'ai travaillé que par moment sur ce toolkit
ce qui explique son état d'avancement ...
Ce toolkit X11 est basé sur une approche simpliste
Il n'y a pas d’algorithme sophistiqué
J'utilise assez largement le polymorphisme
et c'est sans doute le concept le plus avancé du C++ dont je fait usage.
Malgré la simplicité du code,
le toolkit semble relativement optimisé en terme d'affichage
au regard des fonctionnalités qui ont étés implémentées jusqu’à aujourd'hui.
Comme je n'avais pas d'idée de look a donner a ce toolkit j'ai utilisé plus ou moins celui du premier GTK
concernant la partie "feel" du look-n-feel, la partie ergonomie, pour l'instant je n'ai pas définit vraiment de règles.
Message édité par Caffrey's le 02-05-2014 à 03:24:40