feat(android): copy from the list, framed entries, one radius everywhere
The cross moves inside the search field, where it belonged. My first attempt failed with a TLabel, whose HitTest is false by default, and I concluded too quickly that no child of a TEdit can take a touch. A TRectangle takes it. Buttons drop the pill and take the fields' radius: a stadium beside a 14-radius field put two shape vocabularies on one line, and consistency is worth more here than fidelity to the reference. List entries get their own block, like the detail fields - on a gradient a bare line of text has no readable edge. And each row carries User and Pass buttons, so the commonest gesture no longer requires opening the entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,10 @@ const
|
|||||||
COL_MUTED = TAlphaColor($B3E8F0FA);
|
COL_MUTED = TAlphaColor($B3E8F0FA);
|
||||||
COL_ON_ACC = TAlphaColor($FFFFFFFF); // le blanc reste lisible sur 30 %
|
COL_ON_ACC = TAlphaColor($FFFFFFFF); // le blanc reste lisible sur 30 %
|
||||||
|
|
||||||
|
/// Un seul arrondi pour tout ce qui se touche - champs comme boutons. Il
|
||||||
|
/// doit suivre celui de make-style.ps1, qui l'applique aux champs natifs.
|
||||||
|
RADIUS = 14;
|
||||||
|
|
||||||
/// A appeler une fois, avant la creation de la forme.
|
/// A appeler une fois, avant la creation de la forme.
|
||||||
procedure ApplyDarkTheme;
|
procedure ApplyDarkTheme;
|
||||||
|
|
||||||
@@ -105,10 +109,6 @@ type
|
|||||||
procedure Up(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
|
procedure Up(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
|
||||||
X, Y: Single);
|
X, Y: Single);
|
||||||
procedure Leave(Sender: TObject);
|
procedure Leave(Sender: TObject);
|
||||||
/// L'arrondi se recalcule a chaque changement de taille : la hauteur est
|
|
||||||
/// posee APRES la creation, et un arrondi fixe superieur a la moitie de
|
|
||||||
/// celle-ci transforme le bouton en ellipse.
|
|
||||||
procedure Resized(Sender: TObject);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPressFeedback.Down(Sender: TObject; Button: TMouseButton;
|
procedure TPressFeedback.Down(Sender: TObject; Button: TMouseButton;
|
||||||
@@ -128,16 +128,6 @@ begin
|
|||||||
if Sender is TRectangle then TRectangle(Sender).Opacity := 1;
|
if Sender is TRectangle then TRectangle(Sender).Opacity := 1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPressFeedback.Resized(Sender: TObject);
|
|
||||||
var
|
|
||||||
LR: TRectangle;
|
|
||||||
begin
|
|
||||||
if not (Sender is TRectangle) then Exit;
|
|
||||||
LR := TRectangle(Sender);
|
|
||||||
LR.XRadius := LR.Height / 2;
|
|
||||||
LR.YRadius := LR.Height / 2;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function Surface(AParent: TFmxObject): TRectangle;
|
function Surface(AParent: TFmxObject): TRectangle;
|
||||||
begin
|
begin
|
||||||
Result := TRectangle.Create(AParent);
|
Result := TRectangle.Create(AParent);
|
||||||
@@ -178,9 +168,11 @@ var
|
|||||||
begin
|
begin
|
||||||
Result := TRectangle.Create(AParent);
|
Result := TRectangle.Create(AParent);
|
||||||
Result.Parent := AParent;
|
Result.Parent := AParent;
|
||||||
// Pilule : l'arrondi vaut exactement la moitie de la hauteur, et se
|
// MEME ARRONDI QUE LES CHAMPS. Un bouton en pilule a cote d'un champ a 14
|
||||||
// recalcule quand elle change (voir TPressFeedback.Resized). Tout ce qui se
|
// donnait deux vocabulaires de forme sur la meme ligne ; la coherence vaut
|
||||||
// touche est arrondi ainsi dans la reference.
|
// mieux que la fidelite a la reference sur ce point.
|
||||||
|
Result.XRadius := RADIUS;
|
||||||
|
Result.YRadius := RADIUS;
|
||||||
Result.Cursor := crHandPoint;
|
Result.Cursor := crHandPoint;
|
||||||
Result.Stroke.Kind := TBrushKind.None;
|
Result.Stroke.Kind := TBrushKind.None;
|
||||||
// La difference entre principal et secondaire tient a l'opacite du blanc,
|
// La difference entre principal et secondaire tient a l'opacite du blanc,
|
||||||
@@ -208,8 +200,6 @@ begin
|
|||||||
Result.OnMouseDown := LFeedback.Down;
|
Result.OnMouseDown := LFeedback.Down;
|
||||||
Result.OnMouseUp := LFeedback.Up;
|
Result.OnMouseUp := LFeedback.Up;
|
||||||
Result.OnMouseLeave := LFeedback.Leave;
|
Result.OnMouseLeave := LFeedback.Leave;
|
||||||
Result.OnResize := LFeedback.Resized;
|
|
||||||
LFeedback.Resized(Result);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure SetCornerRadius(AButton: TRectangle; AValue: Single);
|
procedure SetCornerRadius(AButton: TRectangle; AValue: Single);
|
||||||
|
|||||||
+75
-39
@@ -185,6 +185,8 @@ type
|
|||||||
|
|
||||||
procedure OnSearchChanged(Sender: TObject);
|
procedure OnSearchChanged(Sender: TObject);
|
||||||
procedure OnSearchClear(Sender: TObject);
|
procedure OnSearchClear(Sender: TObject);
|
||||||
|
procedure OnRowCopyUser(Sender: TObject);
|
||||||
|
procedure OnRowCopyPwd(Sender: TObject);
|
||||||
procedure OnFolderTap(Sender: TObject);
|
procedure OnFolderTap(Sender: TObject);
|
||||||
procedure OnFolderPick(Sender: TObject);
|
procedure OnFolderPick(Sender: TObject);
|
||||||
procedure PopulateFolders;
|
procedure PopulateFolders;
|
||||||
@@ -395,9 +397,8 @@ end;
|
|||||||
|
|
||||||
procedure TForm8.BuildList;
|
procedure TForm8.BuildList;
|
||||||
var
|
var
|
||||||
LBar, LTop, LSearchWrap: TLayout;
|
LBar, LTop: TLayout;
|
||||||
LBtnLock: TRectangle;
|
LBtnLock: TRectangle;
|
||||||
LClearMark: TLabel;
|
|
||||||
LGlass: TCircle;
|
LGlass: TCircle;
|
||||||
LHandle: TLine;
|
LHandle: TLine;
|
||||||
begin
|
begin
|
||||||
@@ -452,41 +453,11 @@ begin
|
|||||||
FBtnFolder.Width := 170;
|
FBtnFolder.Width := 170;
|
||||||
FBtnFolder.Margins.Left := 8;
|
FBtnFolder.Margins.Left := 8;
|
||||||
|
|
||||||
// La croix vit A COTE du champ, pas dedans. Un enfant place dans un TEdit
|
|
||||||
// ne recoit pas les touches de facon fiable : le controle natif les capte
|
|
||||||
// sur toute sa surface. Un conteneur avec un Right et un Client leve en
|
|
||||||
// plus toute question d'ordre.
|
|
||||||
LSearchWrap := TLayout.Create(Self);
|
|
||||||
LSearchWrap.Parent := LTop;
|
|
||||||
LSearchWrap.Align := TAlignLayout.Client;
|
|
||||||
|
|
||||||
// La place de la croix est RESERVEE en permanence : la montrer et la cacher
|
|
||||||
// faisait changer la largeur du champ a chaque frappe, et la ligne sautait.
|
|
||||||
// Seule la croix elle-meme apparait et disparait.
|
|
||||||
FSearchClear := TRectangle.Create(Self);
|
|
||||||
FSearchClear.Parent := LSearchWrap;
|
|
||||||
FSearchClear.Align := TAlignLayout.Right;
|
|
||||||
FSearchClear.Width := 46;
|
|
||||||
FSearchClear.Fill.Kind := TBrushKind.None; // invisible, mais cliquable
|
|
||||||
FSearchClear.Stroke.Kind := TBrushKind.None;
|
|
||||||
FSearchClear.OnClick := OnSearchClear;
|
|
||||||
|
|
||||||
LClearMark := TLabel.Create(FSearchClear);
|
|
||||||
LClearMark.Parent := FSearchClear;
|
|
||||||
LClearMark.Align := TAlignLayout.Client;
|
|
||||||
LClearMark.HitTest := False; // le clic va au rectangle
|
|
||||||
LClearMark.Text := 'X';
|
|
||||||
LClearMark.TextSettings.HorzAlign := TTextAlign.Center;
|
|
||||||
LClearMark.TextSettings.VertAlign := TTextAlign.Center;
|
|
||||||
LClearMark.Visible := False;
|
|
||||||
Tint(LClearMark, 17, True);
|
|
||||||
FSearchClearMark := LClearMark;
|
|
||||||
|
|
||||||
// Le champ reste natif - il faut un clavier - et son fond vient desormais du
|
// Le champ reste natif - il faut un clavier - et son fond vient desormais du
|
||||||
// style, recolore a la palette par make-style.ps1. PAS de rectangle derriere
|
// style, recolore a la palette par make-style.ps1. PAS de rectangle derriere
|
||||||
// lui : il en resultait deux cadres arrondis emboites.
|
// lui : il en resultait deux cadres arrondis emboites.
|
||||||
FSearch := TEdit.Create(Self);
|
FSearch := TEdit.Create(Self);
|
||||||
FSearch.Parent := LSearchWrap;
|
FSearch.Parent := LTop;
|
||||||
FSearch.Align := TAlignLayout.Client;
|
FSearch.Align := TAlignLayout.Client;
|
||||||
// Le texte saisi commence apres la loupe.
|
// Le texte saisi commence apres la loupe.
|
||||||
FSearch.Padding.Left := 32;
|
FSearch.Padding.Left := 32;
|
||||||
@@ -527,6 +498,29 @@ begin
|
|||||||
FSearchHint.Opacity := 0.45;
|
FSearchHint.Opacity := 0.45;
|
||||||
FSearchHint.Text := 'Search';
|
FSearchHint.Text := 'Search';
|
||||||
|
|
||||||
|
// Croix DANS le champ, et non plus a cote. Le premier essai avait echoue
|
||||||
|
// avec un TLabel, dont HitTest vaut faux par defaut ; j'en avais conclu trop
|
||||||
|
// vite qu'un enfant de TEdit ne recoit jamais les touches. Un TRectangle les
|
||||||
|
// prend. Sa place est occupee en permanence pour que la largeur du champ ne
|
||||||
|
// bouge pas ; seule la croix apparait.
|
||||||
|
FSearchClear := TRectangle.Create(FSearch);
|
||||||
|
FSearchClear.Parent := FSearch;
|
||||||
|
FSearchClear.Align := TAlignLayout.Right;
|
||||||
|
FSearchClear.Width := 46;
|
||||||
|
FSearchClear.Fill.Kind := TBrushKind.None; // invisible, mais cliquable
|
||||||
|
FSearchClear.Stroke.Kind := TBrushKind.None;
|
||||||
|
FSearchClear.OnClick := OnSearchClear;
|
||||||
|
|
||||||
|
FSearchClearMark := TLabel.Create(FSearchClear);
|
||||||
|
FSearchClearMark.Parent := FSearchClear;
|
||||||
|
FSearchClearMark.Align := TAlignLayout.Client;
|
||||||
|
FSearchClearMark.HitTest := False; // le clic va au rectangle
|
||||||
|
FSearchClearMark.Text := 'X';
|
||||||
|
FSearchClearMark.TextSettings.HorzAlign := TTextAlign.Center;
|
||||||
|
FSearchClearMark.TextSettings.VertAlign := TTextAlign.Center;
|
||||||
|
FSearchClearMark.Visible := False;
|
||||||
|
Tint(FSearchClearMark, 17, True);
|
||||||
|
|
||||||
// Croix d'effacement, montree seulement quand il y a quelque chose a
|
// Croix d'effacement, montree seulement quand il y a quelque chose a
|
||||||
// effacer. Un TLabel et non un bouton : dans un champ, une croix se touche,
|
// effacer. Un TLabel et non un bouton : dans un champ, une croix se touche,
|
||||||
// elle ne se dessine pas comme une commande.
|
// elle ne se dessine pas comme une commande.
|
||||||
@@ -1363,6 +1357,22 @@ begin
|
|||||||
SetButtonText(FBtnFolder, FFolderNames[FFolderSel]);
|
SetButtonText(FBtnFolder, FFolderNames[FFolderSel]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
/// Copie depuis la liste. Le Tag du bouton porte l'indice dans le coffre, pas
|
||||||
|
/// dans la liste affichee - la recherche renumerote les lignes, pas le coffre.
|
||||||
|
procedure TForm8.OnRowCopyUser(Sender: TObject);
|
||||||
|
begin
|
||||||
|
Touch;
|
||||||
|
if (Sender is TRectangle) and (TRectangle(Sender).Tag < FVault.Count) then
|
||||||
|
CopyToClipboard(FVault.Entry(TRectangle(Sender).Tag).Username, 'Username');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TForm8.OnRowCopyPwd(Sender: TObject);
|
||||||
|
begin
|
||||||
|
Touch;
|
||||||
|
if (Sender is TRectangle) and (TRectangle(Sender).Tag < FVault.Count) then
|
||||||
|
CopyToClipboard(FVault.Entry(TRectangle(Sender).Tag).Password, 'Password');
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TForm8.OnSearchClear(Sender: TObject);
|
procedure TForm8.OnSearchClear(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
FSearch.Text := '';
|
FSearch.Text := '';
|
||||||
@@ -1441,7 +1451,8 @@ var
|
|||||||
LFolder: string;
|
LFolder: string;
|
||||||
LBmp: TBitmap;
|
LBmp: TBitmap;
|
||||||
LImg: TImage;
|
LImg: TImage;
|
||||||
LBody: TLayout;
|
LBody, LActions: TLayout;
|
||||||
|
LCard, LBtnUser, LBtnPwd: TRectangle;
|
||||||
LTitle, LSub: TLabel;
|
LTitle, LSub: TLabel;
|
||||||
begin
|
begin
|
||||||
// ponytail: refonte complete de la liste a chaque frappe. Sur un coffre de
|
// ponytail: refonte complete de la liste a chaque frappe. Sur un coffre de
|
||||||
@@ -1467,8 +1478,33 @@ begin
|
|||||||
E := FVault.Entry(FFiltered[I]);
|
E := FVault.Entry(FFiltered[I]);
|
||||||
LItem := TListBoxItem.Create(FList);
|
LItem := TListBoxItem.Create(FList);
|
||||||
LItem.Parent := FList;
|
LItem.Parent := FList;
|
||||||
LItem.Height := 56;
|
LItem.Height := 76;
|
||||||
LItem.Tag := FFiltered[I]; // indice dans le coffre, pas dans la liste
|
LItem.Tag := FFiltered[I]; // indice dans le coffre, pas dans la liste
|
||||||
|
|
||||||
|
// Chaque entree dans son bloc, comme les champs du detail : sur un fond
|
||||||
|
// en degrade, une simple ligne de texte flotte sans limite lisible.
|
||||||
|
LCard := Surface(LItem);
|
||||||
|
LCard.Align := TAlignLayout.Client;
|
||||||
|
LCard.Margins.Rect := RectF(4, 3, 4, 3);
|
||||||
|
|
||||||
|
// Copier sans ouvrir l'entree - le geste le plus frequent. Les deux
|
||||||
|
// boutons sont poses a la coordonnee dans leur propre conteneur : deux
|
||||||
|
// controles alignes a droite laisseraient leur ordre au hasard.
|
||||||
|
LActions := TLayout.Create(LCard);
|
||||||
|
LActions.Parent := LCard;
|
||||||
|
LActions.Align := TAlignLayout.Right;
|
||||||
|
LActions.Width := 150;
|
||||||
|
LActions.Margins.Rect := RectF(0, 8, 8, 8);
|
||||||
|
|
||||||
|
LBtnUser := FlatButton(LActions, 'User', OnRowCopyUser);
|
||||||
|
LBtnUser.Align := TAlignLayout.None;
|
||||||
|
LBtnUser.SetBounds(0, 0, 70, 54);
|
||||||
|
LBtnUser.Tag := FFiltered[I];
|
||||||
|
|
||||||
|
LBtnPwd := FlatButton(LActions, 'Pass', OnRowCopyPwd);
|
||||||
|
LBtnPwd.Align := TAlignLayout.None;
|
||||||
|
LBtnPwd.SetBounds(76, 0, 70, 54);
|
||||||
|
LBtnPwd.Tag := FFiltered[I];
|
||||||
// CONTENU CONSTRUIT A LA MAIN, sans passer par Text/Detail/Bitmap ni par
|
// CONTENU CONSTRUIT A LA MAIN, sans passer par Text/Detail/Bitmap ni par
|
||||||
// un StyleLookup. Ces gabarits n'existent pas dans tous les styles :
|
// un StyleLookup. Ces gabarits n'existent pas dans tous les styles :
|
||||||
// avec Dark.style, 'listboxitembottomdetail' disparait et emporte
|
// avec Dark.style, 'listboxitembottomdetail' disparait et emporte
|
||||||
@@ -1476,8 +1512,8 @@ begin
|
|||||||
// alignes Left et Client - il n'y a alors pas d'ordre a deviner.
|
// alignes Left et Client - il n'y a alors pas d'ordre a deviner.
|
||||||
if FIcons.TryGetValue(FFiltered[I], LBmp) then
|
if FIcons.TryGetValue(FFiltered[I], LBmp) then
|
||||||
begin
|
begin
|
||||||
LImg := TImage.Create(LItem);
|
LImg := TImage.Create(LCard);
|
||||||
LImg.Parent := LItem;
|
LImg.Parent := LCard;
|
||||||
LImg.Align := TAlignLayout.Left;
|
LImg.Align := TAlignLayout.Left;
|
||||||
LImg.Width := 40;
|
LImg.Width := 40;
|
||||||
LImg.Margins.Rect := RectF(4, 8, 8, 8);
|
LImg.Margins.Rect := RectF(4, 8, 8, 8);
|
||||||
@@ -1485,8 +1521,8 @@ begin
|
|||||||
LImg.Bitmap.Assign(LBmp);
|
LImg.Bitmap.Assign(LBmp);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
LBody := TLayout.Create(LItem);
|
LBody := TLayout.Create(LCard);
|
||||||
LBody.Parent := LItem;
|
LBody.Parent := LCard;
|
||||||
LBody.Align := TAlignLayout.Client;
|
LBody.Align := TAlignLayout.Client;
|
||||||
LBody.Margins.Rect := RectF(8, 4, 8, 4);
|
LBody.Margins.Rect := RectF(8, 4, 8, 4);
|
||||||
LBody.HitTest := False;
|
LBody.HitTest := False;
|
||||||
|
|||||||
Reference in New Issue
Block a user