[Résolu] Ligne "cliquable"...

MickMick Membre
mai 2010 modifié dans API AppKit #1
Bonjour à  tous,

Question certainement très nulle, mais j'ai une custom view dont le contenu est de plusieurs type :
=> Des "points", qui sont des custom view : marche bien car interceptent bien les clics. Pas de soucis car ils ont la forme de petits rectangles.
=> Des lignes qui doivent s'afficher et se déplacer au gré de la position de la souris. J'utilise les tracking rect, et je crée des NSBox utilisées comme séparateurs que j'ajoute en subview. Pas de soucis car ce sont des lignes horizontales ou verticales.

Bref, tant que les objets graphiques sont des subViews, j'arrive à  gérer correctement les évènements.

=> Des NSBezierPath => C'est là  que ça se gâte... Comment faire en sorte que les courbes de bézier puissent intercepter un mouseDown ? Si c'est une ligne droite, je peux tester si le clic se trouve dans le rect occupé par la ligne, mais pour une ligne oblique ou une courbe ? Je suppose que c'est simple, mais j'avoue ne pas trouver la solution. Mes subViews acceptent d'être firstResponder et je peux les dessiner selectionnée (ring bleu), comment faire de même pour des bezierPath ? comment intercepter un mouseDown juste pile poil sur l'épaisseur de la courbe ?

Réponses

  • zoczoc Membre
    mai 2010 modifié #2
    Non, justement, ce n'est pas simple. En gros cela revient à  calculer la distance entre le point du clic et le point le plus proche de la courbe et considérer que la courbe a été cliquée si la distance est inférieure à  une distance donnée.



    C'est un problème mathématique assez complexe dans le cas de courbes quelconques et évidemment il n'y a rien de tout fait... Dans le cas de droites (hormis les cas particuliers des droites horizontales et verticales qui se résolvent facilement par la solution que tu donnes), on y arrive assez facilement à  coup de [url=http://fr.wikipedia.org/wiki/Distance_d'un_point_à _une_droite]produits scalaires et vectoriels[/url].


    Dans le cas d'une courbe de bézier à  4 points de contrôle, il faut résoudre une équation d'ordre 5. C'est franchement loin de mes capacités en Maths (mais j'ai toujours été faché avec les Maths, c'est pourquoi à  la base j'ai une formation d'ingénieur systèmes & réseaux  ??? ), mais le PDF Suivant donne une bonne idée de ce qu'il faut mettre en oeuvre mathématiquement.

    Bon courage ;)


    Edit: NSBezierPath propose une méthode "containsPoint", mais elle permet uniquement de tester si un point est à  l'intérieur de la surface décrite par l'objet NSBezierPath, et pas uniquement le chemin formé par les éléments de l'objet.
  • MickMick Membre
    18:29 modifié #3
    Re-Bonjour,

    En effet, cela demande une usine à  gaz....
    Il y peut-être alors une autre solution que de tracer une courbe de bezier : je connais les couples de points x,y. J'ai une méthode qui convertit au système de coordonnées de la view. J'ai donc découpé l'intervalle des abscisses en 20, et j'ai créé une courbe de bézier avec 20 points et 2 points de contrôle pour chaque (petit calcul de coeff directeur de tangente...). Le résultat est splendide. Belle parabole...
    Peut-être qu'il faut que je teste donc moi-même la distance entre le clic et toutes les courbes de bézier (dont je connais les coordonnées de chaque point). Il suffira que je trouve l'ordonnée sur toutes mes béziers qui correspond à  l'abscisse du clic et que je le compare à  l'ordonnée du clic.
    Je vais tester cela. Je pensais qu'il pouvait y avoir une solution plus "cocoesque". Il est un peu dommage qu'il n'existe pas un containPoint juste pour la courbe... Vu qu'ils se sont pris la tête pour vérifier que c'est dans la surface dessinée...
    J'y pense : mes béziers ne sont pas fermées, peut-être que le containPoint fonctionne alors sur la courbe ?.. Je vais essayer.

    Toujours est-il que je ne peut pas traiter mes courbes comme des objets qui pourraient accepter le firstResponder et utiliser les méthodes associées... C'est dommage.
  • AliGatorAliGator Membre, Modérateur
    18:29 modifié #4
    Cela dépend de ce que tu veux faire.
    Si tu veux détecter qu'un clic pile poil sur la ligne de la courbe de bézier, c'est pas évident, et il n'y a rien de prévu en Cocoa pour ça si je ne m'abuse. Donc y'a p'tet un moyen, mais ça dépend de la complexité de tes courbes de bézier. Tu auras sans doute des calculs à  faire, ou alors trouver un moyen pour transformer ta courbe de bézier ouverte en une courbe fermée (par exemple au lieu d'une parabole qui va de A à  B, ouverte, faire une parabole qui va de A à  B, puis retourne à  A. En prenant en compte l'épaisseur de trait), mais ce n'est pas forcément aussi simple puisque les points de contrôle pour aller de A à  B ne sont pas les mêmes que pour aller de B à  A (ils sont inversés, faut calculer leurs coordonnées relatives)...


    Si tu veux accepte de détecter le clic dans le rectangle englobant de ton BezierPath, c'est peut-être plus simple à  détecter
    De même si tu peux fermer tes BezierPaths, et que la zone qu'ils enferment soit ta zone de détection de clic, c'est plus simple également.
    Si tu as des BezierPath simples au final (cercles, arcs, ... pas des points de contrôle de partout et définis avec des équations de degré 4 ou 5 du coup), ça peut s'arranger en calculant le tout (mais bon va falloir te plonger dans les maths quand même). En décomposant ton NSBezierPath en NSBezierPathElements tu réduis déjà  un peu tes équations, mais bon.

    En tout cas à  ma connaissance il n'y a rien de tout fait pour détecter un clic sur la ligne d'un BezierPath. containsPoint ne détecte que si un point est dans un BezierPath, et encore il ne prend pas en compte l'épaisseur de trait.
  • MickMick Membre
    18:29 modifié #5
    Je vais donc utiliser ma technique de comparaison entre les ordonnées du point de ma courbe de bézier à  l'abscisse de la souris et l'ordonnée de la souris, à  l'épaisseur de trait près. J'ai ces données puisque ma bézier est une bidouille pour ne pas avoir à  tracer 150000 points (xi,yi) pour reproduire une parabole...

    Merci quand même.
  • AliGatorAliGator Membre, Modérateur
    18:29 modifié #6
    Tu veux dire que ta courbe est une fonction ?
    Il peut être intéressant pour résoudre ton problème de connaà®tre les propriétés de tes courbes de bézier utilisées.
    En particulier, je crois comprendre à  tes messages que tes courbes de Bézier représentent une application (ou fonction) mathématique ? Autrement dit, pour toute abscisse x, tu n'as qu'un seul point avec cette abscisse appartenant à  ta courbe (tu n'as pas deux points ayant la même abscisse X, comme ça pourrait être le cas pour une courbe de Bézier quelconque comme un cercle par exemple)
  • Paisible.frPaisible.fr Membre
    18:29 modifié #7
    Petite digression : toute personne qui s'interesse au developpement comprenant l'affichage de formes vectorielles se doit de connaitre le fabuleux framework GCDrawKit  sous licence  "standard BSD license"

    Après libre a chacun de l'utiliser ou non suivant ses besoins et son avis sur le framework.  :P
  • MickMick Membre
    18:29 modifié #8
    Bonjour,

    Ma vue affiche effectivement des couples de points (x,y) correspondant à  des mesures. Mon appli permet ensuite de "fiter" à  l'aide de fonctions classiques en physique (pour l'instant linéaire, affine et parabolique). Une fois les paramètres de la fonction sensée modéliser les mesures trouvés (moindres carrés et autres bidouilles d'optimisation), j'ai donc la meilleure fonction qui relie les abscisses aux ordonnées telle que l'écart entre les ordonnées des mesures et les ordonnées de la fonction modèle soient minimal. Cette fonction, je dois la tracer afin que l'utilisateur de l'appli puisse "voir" le résultat du fit. Pour des affines/linéaires, pas trop de soucis, mais pour les paraboles j'ai bricolé une bézier avec 20 points. je me suis tapé des petits calculs de coeff directeurs de tangente pour créer les points de contrôle de la courbe. Les paraboles sont bien tracées.

    Reste le problème de la gestion des évènements. Par principe, je voulais pouvoir considérer chaque graphe comme des subviews de ma vue, qu'elle puissent se gérer comme un NSReponder... Je pensais que c'était plus "propre" que de faire gérer tout à  ma vue globale. Ainsi, les points de mesures affichés sont des petits rectangles (personnalisable pour leur taille, couleur...). Ce sont des subviews qui gèrent toutes seules les évènements. Je n'ai pas à  tester la position du clic dans la vue globale. Je voulais faire de même avec mes autres éléments graphiques de la vue. Mais cela ne va pas être possible apparemment. C'est donc la vue principale qui devra gérer tous les clics, et à  chaque clic je devrais passer en revue tous les éléments graphiques, et tester si l'abscisse et l'ordonnée coincident avec l'abscisse et l'ordonnée de la souris. C'est évidemment faisable, mais je ne trouve pas ça "propre". Ca fait "bidouille"....
  • zoczoc Membre
    18:29 modifié #10
    Ah ouais tiens  :D :D
  • MickMick Membre
    18:29 modifié #11
    Ok, vraiment pas doué pour chercher dans la doc ....

    Je crois que je vais m'en sortir. Merci à  Ali Gator ! o:)
Connectez-vous ou Inscrivez-vous pour répondre.