Rotation 3D des fenetres
Rocou
Membre
A défaut d'être un nouveau standard Apple, ça semble devenir une mode; les nouvelles versions de Pages et Numbers s'y sont mises: la rotation 3D des fenêtres. J'aimerais bien savoir si cet effet fait partie de Cocoa et si c'est facile à mettre en oeuvre.
La "rotation 3D des fenêtres", c'est cet effet qui consiste à faire tourner une fenêtre sur un axe vertical, à l'instar d'une girouette qui tourne sur son axe. On passe ainsi du recto au verso d'une fenêtre. Je trouve cela très bien en terme d'interface.
Si quelqu'un sait mettre cela en oeuvre, je suis très intéressé. J'ai parcouru la doc et Internet sans rien trouver.
La "rotation 3D des fenêtres", c'est cet effet qui consiste à faire tourner une fenêtre sur un axe vertical, à l'instar d'une girouette qui tourne sur son axe. On passe ainsi du recto au verso d'une fenêtre. Je trouve cela très bien en terme d'interface.
Si quelqu'un sait mettre cela en oeuvre, je suis très intéressé. J'ai parcouru la doc et Internet sans rien trouver.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Depuis 10.5 et l'apparition de NSAnimation/NSViewAnimation, il se peut que cela soit possible de la faire sous cocoa, mais je n'ai pas testé.
Si cela t'intéresse, je peux te donner un bout de code qui anime une fenêtre "en rotation 3D".
Bien sûr que ça m'intéresse!
L'effet de transition entre 2 fenêtres est réalisé par Quartz (le window-server de OS X).
En fait cet effet ne se fait pas entre 2 fenêtres différentes, mais sur 1 seule fenêtre qui aura 2 aspects :
- le premier juste avant la transition,
- et le second juste après.
La création d'une transition se fait en 2 étapes :
- création de la transition,
- lancement de la transition.
C'est entre l'étape de création et l'étape de lancement qu'il faut redessiner la fenêtre dans son nouvel état (celui après transition). Tout ce qui sera redessiné ne se verra pas à l'écran, car lors de la création, la fenêtre à l'écran est remplacée par sa "photo".
Les modifications faites sont stockées dans son backing-store.
Le lancement de la transition se fera donc entre cette photo à l'écran et la fenêtre telle qu'elle est dessinée dans son backing-store.
Je t'ai fait un petit projet de quelques lignes pour illustrer ça.
Le premier zip joint est l'appli de démonstration, et le second est le projet xcode.
Le projet Xcode est 10.5 (utilisation .xib), mais en fait le code peut fonctionner sans modif depuis 10.2.
j'avais déjà choppé ce genre de code.
Mais ce que je voudrais faire moi, c'est ce qu'on voit dans Pages'09 : un flip de la fenêtre des choix de modèles vers le NSOpenPanel pour aller ouvrir un fichier. Je trouve ça terriblement classe et vu que mon appli est "Leopard Only', ce serait une bonne occas de joueur avec core animation.
Un spécialiste CA dans le coin ??
J'avoue n'y connaitre vraiment rien...
Qu'appelles tu un "flip de la fenêtre" ?
C'est quoi comme effet visuel (je n'ai pas Pages) ?
voilà une petite vidéo : http://cocoatreeapplications.com/_videos/FlippingInPages.mov
Ah oui, c'est l'effet flip bien sûr...
Dans le code du projet que j'ai joint, il suffit de changer la ligne
config.type=7;
par
config.type=9;
7 étant le code pour l'effet cube, et 9 pour celui du flip.
D'autres effets sont disponibles : le 8 par exemple provoque un effet de tourbillon.
L'effet d'apple est pas tout à fait pareil, je pense qu'il utilise core animation. Mais la vrai question, c'est comment faire un flip sur le NSOpenPanel ?? D'autant plus que la taille de la fenêtre change contrairement à tous le sexemple de flip de fenêtre que j'ai trouvé jusqu'ici, et qui utilise tous la combine des fonctions quartz non documentées.
D'autant que la plupart des propriétés des CALayer sont CAAnimatable, c'est à dire qu'elles gèrent toutes seules l'animation.
Un exemple simple, tu prends une NSView (créée par exemple dans IB, et sur laquelle tu as un IBOutlet). Si tu as activé CoreAnimation dessus (il faut cocher "Use Backing Store" ou un truc comme ça dans la palette d'informations IB c'est tout en haut de la palette dans une TableView qui liste les vues et sous-vues de la vue sélectionnée), tu pourras alors accéder à myView.layer (qui est un CALayer si tu as activé le Has Backing Store, nil sinon, puisque par défaut les vues n'utilisent pas CoreAnimation inutilement)
Du coup tu peux faire des trucs comme myView.layer.transform = CA3DTransformRotate(M_PI_2,0,1,0) ou qqch dans le genre pour effectuer un effet de rotation 3D de ta vue. Et ça suffit ! Ta vue va tourner autour de l'axe Y d'un angle de âˆ/2, donc elle va commencer l'effet "flip". Il suffit alors de remplacer le contenu de myView par une autre NSView (celle que tu veux afficher à la place de la courante), puis de refaire une rotation autour de Y de âˆ/2 à âˆ.
Le problème c'est qu'autant pour les NSView ça marche très bien, on peut accéder à la property layer qui est un CALayer et faire tout ça. Autant pour les NSWindow on a pas ça. Donc on ne peux pas le faire directement sur une NSWindow...
Je pense que la solution est de créer, juste le temps de l'animation, une NSWindow borderless et à fond transparent, et de lui mettre comme contentView le dessin de la fenêtre (il doit bien y avoir moyen de récupérer le buffer graphique de dessin de la fenêtre pour "dessiner la fenêtre" entière (contenu mais aussi barre de titre, etc, bref faire un snapshot de la fenêtre). Ensuite il suffit d'appliquer une CA3DTransform de âˆ/2 à contentView.layer.transform, de remplacer la contentView par une capture de l'autre fenêtre à afficher, terminer la rotation âˆ/2->âˆ, et une fois l'animation finie, on détruit tout ça (la NSWindow borderless et à fond transparent et sa contentView quoi) et on met la vraie fenêtre finale à la place.
Avantage de cette méthode par rapport à l'utilisation de CGSPrivate.h c'est qu'elle est officiellement documentée car utilise CoreAnimation. Reste à savoir comment faire une "capture de la fenêtre" pour placer l'image de cette fenêtre en tant que ContentView. Je sais que c'est faisable puisque certains membres du forum l'ont fait quand ils ont programmé Fenêtres Volantes... mais je ne sais plus si la méthode utilisée était officielle ou undocumented, alors bon...
Si quelqu'un à une meilleure façon de procéder pour utiliser CoreAnimation pour faire cet effet, moi je n'ai réussi à faire des trucs qu'avec les NSView pas les NSWindows...
Voir l'exemple SonOfGrab pour utiliser la CGWindow API valable depuis 10.5
Si je trouve la motivation un de ces 4 j'essaierai.
Si jamais j'y arrive, je vous tiendrai au courant. Mais vu que ça fait 4 mois que je programme, faut pas pousser mémé dans les ortis...
C'est pas encore parfait, mais ça marchotte :
- Deux catégories dans NSWindow_Additions.h, la principale étant pour obtenir une NSImage à partir d'une NSWindow (capture). (l'autre c'est juste pour positionner plus facilement une fenêtre d'après son centre)
- Une classe WindowTransitionner qui permet de configurer puis effectuer une transition entre 2 fenêtres. Ca utilise le principe que j'ai décrit plus haut, à savoir faire une capture des 2 fenêtres, créer une fenêtre transparente et borderless et mettre l'image de la capture dedans, et utiliser CoreAnimation pour faire l'effet de rotation 3D ([tt]CATransform3DMakeRotation(angle,x,y,z)[/tt]).
Le tout marche pas trop mal, j'ai même permis de régler l'angle de rotation (x,y) si on veut. Reste quand même quelques imperfections :
- si on choisit un angle de rotation autre que 0 ou 90°, ma fenêtre transparente (servant le temps de l'animation) n'est pas assez grande et du coup y'a un effet de clipping sur l'animation. Bon le choix de l'axe de rotation en mm temps je l'ai ajouté vite fait pour montrer le principe, voulais pas me fouler plus que ça là dedans.
- Un effet de flicker est éventuellement visible au moment de la capture, ceci est dû au fait que je force la fenêtre à s'afficher (orderFront) pour pouvoir appeler la méthode CG qui fait la capture (et remet en orderOut ensuite si elle n'était pas visible avant), car j'ai remarqué que sinon ça donnait une image vide. Je suis prenneur d'une modif sur mon code qui permettrait de capturer aussi les fenêtres OffScreen pour éviter ça.
- Il n'y a pas les ombres pendant la rotation. C'est à la fois voulu parce que plus simple et surtout m'évite de calculer la taille d'image qui me sera nécessaire (plus grande que la fenêtre à cause des ombres donc) et le positionnement de tout ça (l'effet d'ombre en haut et à gauche, bien que bien plus léger qu'à droite et en bas, existe et crée un décalage aussi entre les coordonnées de la fenêtre et celles de l'image générée...)
Mais bon voilà je vous livre ça en l'état, tout commentaire étant le bienvenu, c'est histoire de montrer où j'en suis arrivé si ça peut servir à qqun quoi
Oui, merci beaucoup!
Si on pouvait supprimer l'effet de flickr et avoir une animation "en perspective", ce serait super. EN fait là on a pas vraiment l'impression d'un retournement. On dirait plutot que la fenêtre se "collapse" sur elle-meme puis se réétire en une nouvelle..
Vais voir si c'est jouable.
Bon après le principe pour moi c'était de découvrir CoreAnimation (c'était mon premier projet utilisant CA), et c'est sûr qu'utiliser CGSPrivate.h rend quand même mieux.
Le seul truc qui me gène c'est l'aspect undocumented de la chose dans l'utilisation des méthodes CoreGraphics directement... faudrait que ça s'officialisse en fait... maintenant CGSPrivate.h commence à être connu et utilisé on le trouve un peu partout sur le net, donc bon faut juste espérer que ça disparaisse pas prochainement quoi. Mais sinon le rendu est forcément mieux car c'est justement à priori ce qu'Apple utilise en interne, donc c'est bien l'effet attendu qu'on obtient on a pas à le "recréer" avec CoreAnim.
Bon faut que je me plonge encore plus dans CoreAnimation pour voir si on peut ajouter de la perspective
En fait pour la perspective, c'est simple il suffit de modifier directement la matrice de transformation pour mettre un facteur multiplicateur sur le Z (atome "m34" de la matrice de transformation 3D) : Voilà pour l'idée...
J'ai rajouté quelques marges pour la "fenêtre transparente" de sorte qu'elle soit plus grande et que ça évite un peu plus de couper, mais bon idéalement faudrait calculer cette marge au mieux (la flemme ^^)
Voilà donc la nouvelle version
Super boulot, j'ai beaucoup appris grâce à ton code. C'est un peu lourdingue pour un simple effet de flip mais j'ai beaucoup progressé. Merci.
Pour plus d'infos je te conseille le PDF "Xcode animations" de Ankur Kothari (que tu trouveras par exemple ici), qui date quand même pas mal (c'est du Xcode et du IB 2 donc faut adapter les manips pour Xcode et IB 3) mais bon le code lui ne change pas ; ou sa version HTML sur son blog, ici.
Le header officiel CGSPrivate.h publié par Richard Wareham est quant à lui dispo chez CocoaDev entre autres, chez qui tu trouveras aussi des liens vers des pages et exemples intéressants pour faire tous ces effets directement avec les fonctions undocumented d'Apple. Et vu depuis le temps qu'elles existent et qu'elles sont encore présente, je pense qu'il n'y a pas trop de risque... il faut juste espérer ne pas les voir disparaà®tre dans Snow Leopard... ou alors si c'est le cas que ce soit en contrepartie de la publication d'une API équivalente officielle
Après, ça n'empêche pas d'utiliser CoreAnimation pour d'autres effets sympas
Bon, les résultats :
1. c'est 100 % api privée.
2. l'effet utilise CGSSetWindowWarp, la même fonction qui permet les effets de miniaturisation dans le dock.
J'ai trouvé le truc en espionnant Pages (via Instruments) et en y trouvant la présence de cette fonction.
Pour être sûr que c'est elle qui est utilisée lors de cette transition, j'ai fait comme pour la miniaturisation des fenêtres : l'appui sur SHIFT au moment du lancement l'effet ralentit ce dernier.
CGSSetWindowWarp permet de déformer les fenêtres (toutes les déformations sont presques possibles car ça utilise un mesh de déformation) en temps réel.
L'effet donc déforme la première fenêtre (celle des modèles). Puis au milieu de la transition, elle est rendue invisible. Au même moment, le panel est rendu visible et utilise la même déformation que la fenêtre juste avant sa disparition. Enfin, la transition repart dans l'autre sens permettant au panel de redevenir "normal".
CQFD.
Comment ça faire pareil ?
Tu veux utiliser la même fonction privée ?
Ou tu veux simuler cette fonction en quelque chose de moins undocumented ?
mais coment je faire avec la fenêtre du NSOpenPanel. ?
Oui, elles sont instantanées.
Le fonctionnement du warping repose sur une matrice qui associe pixels réels (ceux de la fenêtre) et pixels d'affichage (ce qui apparaitra à l'écran).
le warping peut être mis sur une fenêtre non-affichée (cas du openPanel par exemple). Il suffit d'avoir son window-id.
Au moment de son affichage, le warping agira sur son aspect.
En gros, le tuto travaille comme ce qu'Aligator a exposé plus haut (à savoir 2 layers et une fenêtre transparente).
Le plus est que l'effet de rotation est vraiment réelle (car celui d'Ali reste un peu artificiel).
Mais tout comme Ali, reste le problème d'acquérir l'image des fenêtres. Et là , je me heurte aux mêmes problèmes.
Donc, je pense que le warping est pour le moment la seule solutions acceptable (du moins si on considère qu'utiliser des undocumented est acceptable).
Je suis donc à la recherche de la formule mathématique géniale qui permettrait d'avoir la série de 4 points (pour les 4 extrémités d'une fenêtre) dans l'espace écran pour visuellement simuler cette rotation.
On peut peut-être t'aider là . Tu peux préciser les contraintes ? C'est celles de la video initiale de ce post ? Cela semble une rotation 3D autour d'un axe qui est en z<0, et x peut être variable, ce qui donne un effet de perspective.
Sans rien dire depuis le début de ce post,ce qui il faut avouer est assez rare ::)) , je cherche dans la direction de l'animation des filtres que je n'avais pas complètement dévissé dans la partie filtre du tuto core animation
Je travaille dans 2 directions :
1. celle qui utilise les méthodes officielles (layers et animation),
2. celle qui utilise le warping des fenêtres (donc undocumented).
Dans le premier cas, l'effet flip est presque au point.
J'ai utilisé le tuto LemurFlip pour réaliser ça.
Comme dit plus haut, en mixant ce tuto et les explications d'Aligator, j'obtiens bien un bel effet entre les 2 fenêtres. Le problème consiste à transformer ces fenêtres en images pour les layers.
Là la situation est correcte pour la fenêtre de départ car elle est visible (donc "photographiable").
Par contre, la fenêtre d'arrivée est non visible (donc impossible d'avoir son image).
J'ai partiellement résolu le problème est la rendant visible, puis photo, puis à nouveau invisible. Pour éviter l'effet de flash qu'Aligator a rencontré, j'ai suspendu la mise à jour de l'affichage (NSDisableScreenUpdates).
Mais ça ne me plait qu'à moitié : ça change l'ordre des fenêtres, et la gestion de la key-window n'est pas parfait.
Dans le second cas, j'aimerai jouer avec le warping.
Ici, il me suffit pour chaque point des coins de la fenêtre de départ obtenir la suite de points simulant l'effet de rotation.
Un dessin étant toujours plus clair qu'un long discours, je joins un croquis.
Dans ce croquis, l'axe de rotation est le centre vertical de la fenêtre (trait bleu foncé).
La fenêtre en position de départ est en contour rouge.
Puis cette fenêtre doit "tourner" (contour vert, orange et rouge) jusqu'à disparaitre (car on ne voit plus que sa tranche).
La formule consiste donc à récupérer les coordonnées des points bleus, verts, oranges et rouges (dans le cas d'une rotation en 4 mouvements) dessinées sur le croquis, et ceci pour le coin haut gauche.
En appliquant la même formule pour les 3 autres points, je pense que l'effet de rotation sera raisonnable.
Après il me restait le pb du ScreenUpdate en effet qui faisait flasher l'écran, je ne connaissais pas cette possibilité de désactiver le refresh écran.
Et le seul autre pb qui me restait de mémoire, c'était la taille de ma fenêtre transparente, mais c'était plus de la flemme qu'autre chose : l'idéal pour éviter tout clipping est de faire en sorte que la fenêtre transparente prenne tout l'écran, et positionner correctement les NSViews qui serviront à la rotation... en faisant gaffe à l'anchorPoint de la rotation... Ou encore (sans doute plus simple si ça marche mais j'ai pas essayé) désactiver le clipping des layers (je sais que pour les CALayers c'est possible, clipping activé par défaut comme pour les Views, mais désactivable)