Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,82 @@ without touching the file's content.
- PDFs: ExifTool appends its changes as an update, so earlier values stay recoverable inside the file; the window says
so.

A group's ring scales with the donut: its gap, depth and narrowest slice (`DonutMenu.SubGap`, `SubBand`,
`DonutGeometry.MinSubWidth`) and the window's room for it (`SubRingSpaceFor`) are multiplied by the ring scale,
radius ÷ 150 × *Group ring size* (`DonutSettings.GroupRingSize`, 75–150 %), never below the default ring of the
smallest donut (`MinRingScale`). *Group ring spread* (`GroupRingSpread`, 100–300 %) widens each ring slice after the
narrowest-slice rule, up to one turn round the donut. Pointer tolerances (`BackdropMargin`, `LeaveEdge`) stay in
pixels. At the defaults and radius 150 the ring is the same as before these settings.

### Slice labels

`Controls/SliceLabelLayout.cs` decides what each slice shows and where, without drawing; `DonutMenu.DrawSliceLabel`
draws the result. Every label stays inside the slice's *safe zone*: the slice inset by a margin of `max(2·s,
0.025·depth)` from all four edges (plus half the 3 px gap on the sides), and as far from a group's chevron: the mark
itself, not a band across the slice, so a group slice's label is centred as any other's. The margin is kept from the
letters, measured from their outlines (`LabelText.InkBox`) with each line at its own width (`StackInside`), not from the
lines' boxes, which hold room above and below the letters: it leaves about the room today's donut leaves round its
names. A box's edges are checked against the inner edge too, since it curves inward. `s = OuterRadius / 150` is the
donut scale; names start at `max(10, 13 × LabelScale × s)` px, captions 80 % of that, icons 20·k beside a name or
16–28·k alone (k = LabelScale · s).

What a slice shows is its preset's `Show` (`LabelShow`: Auto, IconAndText, TextOnly, IconOnly), else its donut's
`ToolbarProfile.LabelShow`, else Auto. `PlanRing` lays out a whole ring at once: one name size, one way for its names
and one icon size, and for each slice the best its Show allows there. The ring's size runs from the starting one down,
0.5 px at a time, to `max(10, 10 × s)` (`SizesFor`; the icon beside a name shrinks with it, to 16·k), and the ring takes
the size and way with the fewest slices it can't label, then the fewest needed captions lost, then the fewest names cut
so far they no longer read (under four letters or 40 % of them) or by only a letter or two, then the earlier way, then
the largest size: on the face a name that doesn't fit is shortened rather than the ring made smaller (a group's ring, of
short words, steps down before it shortens), and optional captions never make the names smaller. Icon & text keeps its
icons: where an icon and its name, whole or shortened to `MinKept`, don't fit (the eight-slice back faces), the ring
steps down rather than cut the name to a word ("Merge…"), which says less. A block goes on the slice's middle first, its
letters centred there and moved sideways until it has as much room left of it as right of it (`Balanced`: a slice that
runs aslant has more on one side); there it drops a caption it doesn't need before it moves. Upright lines wrap at the
slice's width at its middle less 10 px (`ComfortableWidth`, as today's donut wraps its names), and run longer only where
no wrapping fits. Only a block that fits nowhere on the middle is tried along the middle line and round from it
(`Positions`): a three-slice face's slices run aslant. Auto: icon and whole name where every Auto slice of the ring has
room for both and loses nothing for it (the caption it shows without its icon, and its place on the middle), else the
whole name alone, else shortened to no less than 60 % of its letters and never dropping a word (`MinKept`), else
shorter, else its icon alone. Icon & text: icon and whole name, else (a slice that needs its caption) the name and
caption alone, else icon and name shortened to `MinKept` (wrapped as a name alone is: "Remove / metad…"), else the icon
alone. Text only: the whole name, else shortened to `MinKept`, else as far as it takes. Slices of one name look alike:
where one shows its name alone, the others do too. Captions show per slice, under a name where they fit, with Show
output format on. A caption the slice needs (`NeedsCaption`: "Copy to folder" or "Move to folder" under a folder's name,
or the format under a name another slice of the ring has too) shows with it off as well, and under the name beside an
icon; an icon otherwise stands in for the caption. A slice without an icon (a format conversion) shows its name and
doesn't decide the others' style. Icon and name: on a deep slice the icon above the name, both upright, else (where that
can't show the name whole, or for Out from the centre) the icon before a name running out from the centre; on a wide
slice the upright icon above a name bent along the ring, above it as read (the outer of the two on the top half, the
inner on the bottom), else (a group ring's chip, too shallow for one above the other at most sizes) before it, side by
side. The icon never turns. A ring takes one placement for all its icons (above their names where every one fits so, the
face stepping down for it; else before them) and names, the one of its way (`ways` in `PlanRing`): straight under an
upright icon, out from the centre after the icon, or bent beside it; a format's name beside them runs that way too, and
a ring bends only if every name of it can (else all run level with it). Names wrap at word boundaries (CJK by character,
Korean by word) and run one way for the whole ring, each way through the sizes, except a slice whose preset says its own
(`Preset.Direction`: Straight, OutFromCentre, AlongRing; `SliceLabelLayout.Tries`). The donut's face keeps its names
upright, and so does a group's ring of Chinese, Japanese or Korean names (turned, their characters read sideways). A
group's ring, deeper than it is wide, tries straight or out from the centre, then bent; one wider than deep (`IsWide`,
so the ring's size, its spread and the name size decide) bent along the ring, then straight, then out from the centre. A
bent name (`LabelText.Curved`) puts each line on its own circle and each letter on it, upright, clockwise on the top
half and anticlockwise on the bottom (`CurvedGlyphs`); it fits when its lines stay between the zone's circles and their
ends clear the side edges at their inner edge (`CurvedFits`). Scripts whose letters join or reorder (Arabic, Hebrew, the
Indic scripts, Thai, Lao, Myanmar) aren't bent but drawn level with the ring. A shortened name runs in the direction
that keeps the most of it. `DonutMenu.LabelPlan` caches each ring's plan by its inputs (items, geometry, scale,
settings), so labels are laid out once per change, not per frame; the open, pop and flip animations move them with
transforms. Every group's ring slices are equally wide, whatever the group's size (`DonutGeometry.SubSpan`: a share of
the group's slice and a quarter of each neighbour, as if three slices shared it, times Group ring spread), so a group of
six fans out twice as far as a group of three and the spread looks the same on every group; it never goes round more
than once. A group's ring labels follow the ring's scale instead of the donut's, and the Label scale as far as the
ring's slices have room: `PlanRingToFit` steps the Label scale down 5 % at a time, not below 100 %, until every slice
shows its whole name or its icon.

The icons are a curated set of Material Design Icons (`SliceIcons.Data.cs`, generated; Apache-2.0, see
THIRD-PARTY-NOTICES.md), keyed by their MDI names and grouped for the picker. `Preset.Icon` is a key, `"none"`, or
unset for the default: a built-in preset's own (by id), else its tool's; plain format conversions have none.
`Preset.ShortName` replaces the name on the donut and its rings (`DonutName` / `RingName`); the centre hint always
names the slice in full (`DonutItem.FullName`). The preset and group editors share `SliceLabelFields` (Icon with
`IconPickerFlyout`, Short name, Show); the Toolbar page sets each donut's Slice labels and Label scale.

## Crop overlay and PDF page editor

The image Crop tool (`tool-crop`) and PDF Crop (`tool-pdf-crop`) open `Views/CropOverlayWindow` instead of running a job:
Expand Down
41 changes: 41 additions & 0 deletions src/Filee.App/Assets/i18n/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@
"hide.state_done": "تم",
"preset.clip": "قص مقطع",
"preset.group_crop_video": "اقتصاص إطار الفيديو",
"preset.group_crop_video.donut": "اقتصاص إطار الفيديو",
"preset.crop_video_auto": "إزالة الأشرطة السوداء",
"preset.crop_video_auto.short": "تلقائي",
"preset.crop_video_16_9": "اقتصاص إلى 16:9",
Expand Down Expand Up @@ -427,6 +428,7 @@
"image_edit.unit.Inches": "بوصة",
"preset.grayscale": "تدرّج رمادي",
"preset.remove_background": "إزالة الخلفية",
"preset.remove_background.donut": "إزالة الخلفية",
"preset.trim_transparent": "قص الحواف الشفافة",
"preset.group_rotate": "تدوير",
"preset.rotate_left": "تدوير لليسار",
Expand All @@ -444,6 +446,7 @@
"preset.flip_vertical": "انعكاس عمودي",
"preset.flip_vertical.short": "عمودي",
"preset.merge_pdf": "دمج PDF",
"preset.merge_pdf.donut": "دمج PDF",
"merge.order_title": "اختيار ترتيب الدمج",
"merge.order_hint": "استخدم الأسهم للتنقل بين البطاقات. لتغيير الترتيب، اسحب بطاقة أو استخدم Alt + ↑ / Alt + ↓. تُدمج الملفات من الأعلى إلى الأسفل. مفتاح Delete يحذف الملف المحدد.",
"merge.sort_by": "ترتيب حسب",
Expand Down Expand Up @@ -500,11 +503,13 @@
"pdf_editor.saved": "تم الحفظ: {0}",
"preset.split_pdf": "تقسيم PDF",
"preset.compress_pdf": "ضغط PDF",
"preset.compress_pdf.donut": "ضغط PDF",
"preset.searchable_pdf": "PDF قابل للبحث",
"preset.long_image": "صورة طويلة",
"preset.pdf_slides": "PPTX (شرائح)",
"preset.extract": "استخراج",
"preset.zip_all": "ZIP واحد",
"preset.zip_all.donut": "ZIP واحد",
"preset.extract_subtitles": "استخراج الترجمة",
"preset.caption_video": "تسميات توضيحية",
"preset.caption_video.donut": "تسميات",
Expand All @@ -513,23 +518,30 @@
"preset.trace_line_art": "رسم خطي إلى SVG",
"preset.trace_line_art.donut": "رسم خطي إلى SVG",
"preset.extract_frames_png": "استخراج الإطارات (PNG)",
"preset.extract_frames_png.donut": "استخراج الإطارات (PNG)",
"preset.extract_frames_jpg": "استخراج الإطارات (JPG)",
"preset.extract_motion": "استخراج فيديو Live Photo",
"preset.live_photo_iphone": "Live Photo (iPhone)",
"preset.motion_photo_android": "Motion Photo (Android)",
"preset.compress_image": "ضغط الصورة",
"preset.compress_image.donut": "ضغط الصورة",
"preset.compress_video": "ضغط الفيديو",
"preset.compress_video.donut": "ضغط الفيديو",
"preset.for_email": "فيديو للبريد الإلكتروني (أقل من 18 ميغابايت)",
"preset.for_discord": "فيديو لـ Discord (أقل من 10 ميغابايت)",
"preset.for_whatsapp": "فيديو لـ WhatsApp (720p، أقل من 16 ميغابايت)",
"preset.for_instagram": "صورة لـ Instagram (JPG، بعرض 1080 بكسل)",
"preset.video_from_images": "فيديو من الصور",
"preset.extract_audio": "استخراج الصوت",
"preset.merge_videos": "دمج الفيديوهات",
"preset.merge_videos.donut": "دمج الفيديوهات",
"preset.compress_audio": "ضغط الصوت",
"preset.merge_audio": "دمج الصوت",
"preset.merge_audio.donut": "دمج الصوت",
"preset.merge_images": "دمج الصور",
"preset.merge_images.donut": "دمج الصور",
"preset.remove_metadata": "إزالة البيانات الوصفية",
"preset.remove_metadata.donut": "إزالة البيانات الوصفية",
"format.folder": "مجلد",
"format.category.Image": "صورة",
"format.category.Pdf": "PDF",
Expand Down Expand Up @@ -725,6 +737,14 @@
"toolbar.hole": "الفتحة الوسطى",
"toolbar.opacity": "العتامة",
"toolbar.captions": "إظهار صيغة الناتج",
"toolbar.slice_labels": "تسميات القطاعات",
"toolbar.label_scale": "حجم التسميات",
"toolbar.ring_size": "حجم حلقة المجموعة",
"toolbar.ring_spread": "اتساع حلقة المجموعة",
"labels.auto": "تلقائي",
"labels.icon_text": "أيقونة ونص",
"labels.text_only": "نص فقط",
"labels.icon_only": "أيقونة فقط",
"toolbar.new_preset": "إعداد مسبق جديد",
"toolbar.new_group": "مجموعة جديدة",
"toolbar.face_front": "الأمام",
Expand Down Expand Up @@ -788,6 +808,27 @@
"presets.save": "حفظ",
"presets.revert": "تراجع عن التغييرات",
"presets.name": "الاسم",
"presets.icon": "الأيقونة",
"presets.icon_default": "الافتراضية",
"presets.icon_none": "بلا",
"presets.icon_search": "ابحث عن أيقونة",
"presets.icon_no_match": "لا توجد أيقونة تطابق البحث.",
"presets.short_name": "اسم مختصر",
"presets.short_name_hint": "يظهر على الشريط الدائري بدل الاسم",
"presets.show": "ما يظهر على القطاع",
"presets.show_donut": "إعداد الشريط الدائري",
"presets.direction": "اتجاه النص",
"labels.dir_straight": "مستقيم",
"labels.dir_out": "من المركز إلى الخارج",
"labels.dir_ring": "على طول الحلقة",
"icons.cat.files_and_folders": "الملفات والمجلدات",
"icons.cat.pictures": "الصور",
"icons.cat.crop_and_transform": "القص والتحويل",
"icons.cat.pdf_and_pages": "PDF والصفحات",
"icons.cat.video_and_audio": "الفيديو والصوت",
"icons.cat.text_and_data": "النص والبيانات",
"icons.cat.share_and_move": "المشاركة والنقل",
"icons.cat.general": "عام",
"presets.target": "التحويل إلى",
"presets.target_hidden": "الهدف الحالي ({0}) ينتمي فقط إلى مجالات معطلة، لذا لا يظهر في القائمة. سيبقى الهدف ما لم تختر هدفًا آخر.",
"presets.same_as_source": "صيغة الملف الأصلي نفسها",
Expand Down
Loading
Loading