Skip to content

Commit

Permalink
Working on the presentation of the tags
Browse files Browse the repository at this point in the history
  • Loading branch information
mikekazakov committed Jan 17, 2024
1 parent 2b098b0 commit 7467a7d
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 2 deletions.
22 changes: 20 additions & 2 deletions Source/Panel/Panel.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
CF4652592699F97C0085840A /* PanelViewFieldEditor.h in Headers */ = {isa = PBXBuildFile; fileRef = CF4652582699F97C0085840A /* PanelViewFieldEditor.h */; };
CF46525D2699F9830085840A /* PanelViewFieldEditor.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF46525C2699F9830085840A /* PanelViewFieldEditor.mm */; };
CF465284269A4C150085840A /* libOperations.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF465283269A4C150085840A /* libOperations.a */; };
CF5D1DCC2B56E5C000750174 /* TagsPresentation.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF5D1DCB2B56E5C000750174 /* TagsPresentation.mm */; };
CF5D1DCF2B58798A00750174 /* TagsPresentation_UT.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF5D1DCE2B58798A00750174 /* TagsPresentation_UT.mm */; };
CF60DF252A6D3BAB00478BA0 /* libTerm.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF60DF242A6D3BAB00478BA0 /* libTerm.a */; };
CF60DF272A6D47CB00478BA0 /* ExternalTools_IT.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF60DF262A6D47CB00478BA0 /* ExternalTools_IT.mm */; };
CF739C3D295644CD004758C5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CF739C3B295644CD004758C5 /* Localizable.strings */; };
Expand Down Expand Up @@ -111,6 +113,9 @@
CF4652582699F97C0085840A /* PanelViewFieldEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PanelViewFieldEditor.h; path = include/Panel/PanelViewFieldEditor.h; sourceTree = "<group>"; };
CF46525C2699F9830085840A /* PanelViewFieldEditor.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = PanelViewFieldEditor.mm; path = source/PanelViewFieldEditor.mm; sourceTree = "<group>"; };
CF465283269A4C150085840A /* libOperations.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libOperations.a; sourceTree = BUILT_PRODUCTS_DIR; };
CF5D1DCA2B56E5AE00750174 /* TagsPresentation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TagsPresentation.h; path = include/Panel/UI/TagsPresentation.h; sourceTree = "<group>"; };
CF5D1DCB2B56E5C000750174 /* TagsPresentation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = TagsPresentation.mm; path = source/UI/TagsPresentation.mm; sourceTree = "<group>"; };
CF5D1DCE2B58798A00750174 /* TagsPresentation_UT.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = TagsPresentation_UT.mm; path = tests/UI/TagsPresentation_UT.mm; sourceTree = "<group>"; };
CF60DF242A6D3BAB00478BA0 /* libTerm.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libTerm.a; sourceTree = BUILT_PRODUCTS_DIR; };
CF60DF262A6D47CB00478BA0 /* ExternalTools_IT.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ExternalTools_IT.mm; path = tests/ExternalTools_IT.mm; sourceTree = "<group>"; };
CF739C3C295644CD004758C5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -192,19 +197,29 @@
CF0B0403281F013800076FDF /* UI */ = {
isa = PBXGroup;
children = (
CF0B0404281F014800076FDF /* SelectionWithMaskPopupViewController.h */,
CFCB68FF28968C3600086E40 /* PanelViewPresentationItemsColoringFilter.h */,
CFCB68FE28968C3600086E40 /* PanelViewPresentationItemsColoringFilterPersistence.h */,
CF0B0404281F014800076FDF /* SelectionWithMaskPopupViewController.h */,
CF5D1DCA2B56E5AE00750174 /* TagsPresentation.h */,
);
name = UI;
sourceTree = "<group>";
};
CF0B0406281F015A00076FDF /* UI */ = {
isa = PBXGroup;
children = (
CF0B0407281F016300076FDF /* SelectionWithMaskPopupViewController.mm */,
CFCB690128968C4800086E40 /* PanelViewPresentationItemsColoringFilter.mm */,
CFCB690028968C4800086E40 /* PanelViewPresentationItemsColoringFilterPersistence.mm */,
CF0B0407281F016300076FDF /* SelectionWithMaskPopupViewController.mm */,
CF5D1DCB2B56E5C000750174 /* TagsPresentation.mm */,
);
name = UI;
sourceTree = "<group>";
};
CF5D1DCD2B58797500750174 /* UI */ = {
isa = PBXGroup;
children = (
CF5D1DCE2B58798A00750174 /* TagsPresentation_UT.mm */,
);
name = UI;
sourceTree = "<group>";
Expand Down Expand Up @@ -296,6 +311,7 @@
CFF33FEF25569DBE00B3C92C /* Tests */ = {
isa = PBXGroup;
children = (
CF5D1DCD2B58797500750174 /* UI */,
CF349B0D25FCAE9B009735DC /* Comparators_UT.mm */,
CF60DF262A6D47CB00478BA0 /* ExternalTools_IT.mm */,
CF22060827B9B73C008EDE3A /* ExternalTools_UT.mm */,
Expand Down Expand Up @@ -480,6 +496,7 @@
CFCB690228968C4800086E40 /* PanelViewPresentationItemsColoringFilterPersistence.mm in Sources */,
CF0B040B281F027900076FDF /* Internal.mm in Sources */,
CF46522D268D16500085840A /* Log.cpp in Sources */,
CF5D1DCC2B56E5C000750174 /* TagsPresentation.mm in Sources */,
CFA89D842804C87700BEA127 /* FindFilesData.cpp in Sources */,
CFF33FBE255695BF00B3C92C /* PanelDataExternalEntryKey.cpp in Sources */,
CFF33FA02556948900B3C92C /* PanelDataSortMode.cpp in Sources */,
Expand Down Expand Up @@ -507,6 +524,7 @@
CF3ED50925860E1000D67AF2 /* QuickSearch_UT.mm in Sources */,
CF96DC7629CF4610003EC4EB /* ItemVolatileData_UT.mm in Sources */,
CFF3401625569EC600B3C92C /* PanelData_UT.mm in Sources */,
CF5D1DCF2B58798A00750174 /* TagsPresentation_UT.mm in Sources */,
CFF33FF225569DF300B3C92C /* Tests.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
33 changes: 33 additions & 0 deletions Source/Panel/include/Panel/UI/TagsPresentation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (C) 2024 Michael Kazakov. Subject to GNU General Public License version 3.
#pragma once
#include <Utility/Tags.h>
#include <span>
#include <Cocoa/Cocoa.h>

namespace nc::panel {

struct TrailingTagsInplaceDisplay {
static constexpr int MaxDrawn = 3;
static constexpr int Diameter = 10;
static constexpr int Step = 5;
static constexpr int Margin = 8;

struct Geom {
int width = 0;
int margin = 0;
};

// Provides informations about required space to draw the specified set of tags
static Geom Place(std::span<const utility::Tags::Tag> _tags) noexcept;

// Draws the specified set of tags in the current context.
// if _accent is given it is used to stroke the tags, natural stroke colors is used otherwise.
// background colors is used when more than one tag is drawn.
static void Draw(double _offset_x,
double _view_height,
std::span<const utility::Tags::Tag> _tags,
NSColor *_accent,
NSColor *_background) noexcept;
};

} // namespace nc::panel
117 changes: 117 additions & 0 deletions Source/Panel/source/UI/TagsPresentation.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (C) 2024 Michael Kazakov. Subject to GNU General Public License version 3.
#include "UI/TagsPresentation.h"
#include <Cocoa/Cocoa.h>
#include <array>
#include <utility>
#include <numeric>
#include <algorithm>
#include <ranges>

namespace nc::panel {

static NSColor *Saturate(NSColor *_color) noexcept
{
double factor = 1.5;
double hue, saturation, brightness, alpha;
[_color getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha];
return [NSColor colorWithCalibratedHue:hue
saturation:std::min(1.0, saturation * factor)
brightness:brightness
alpha:alpha];
}

// fill, stroke
static std::pair<NSColor *, NSColor *> Color(utility::Tags::Color _color) noexcept
{
assert(std::to_underlying(_color) < 8);
[[clang::no_destroy]] static std::array<NSColor *, 8> fill_colors;
[[clang::no_destroy]] static std::array<NSColor *, 8> stroke_colors;
static std::once_flag once;
std::call_once(once, [] {
// TODO: explicit components for stroke
constexpr int components[8][4] = {{0, 0, 0, 0},
{147, 147, 151, 255},
{71, 208, 83, 255},
{174, 88, 220, 255},
{42, 125, 252, 255},
{252, 205, 40, 255},
{252, 73, 72, 255},
{252, 154, 40, 255}};

for( size_t i = 0; i < 8; ++i ) {
fill_colors[i] = [NSColor colorWithCalibratedRed:components[i][0] / 255.
green:components[i][1] / 255.
blue:components[i][2] / 255.
alpha:components[i][3] / 255.];
stroke_colors[i] = Saturate(fill_colors[i]);
}
});
auto idx = std::to_underlying(_color);
return {fill_colors[idx], stroke_colors[idx]};
}

TrailingTagsInplaceDisplay::Geom
TrailingTagsInplaceDisplay::Place(const std::span<const utility::Tags::Tag> _tags) noexcept
{
auto count = std::ranges::count_if(_tags, [](auto &_tag) { return _tag.Color() != utility::Tags::Color::None; });
if( count == 0 )
return {};
return {Diameter + (std::min(static_cast<int>(count), MaxDrawn) - 1) * Step, Margin};
}

void TrailingTagsInplaceDisplay::Draw(const double _offset_x,
const double _view_height,
const std::span<const utility::Tags::Tag> _tags,
NSColor *_accent,
NSColor *_background) noexcept
{
if( _tags.empty() )
return;

// Take up to MaxDrawn tags that have a color other than None
std::array<utility::Tags::Color, MaxDrawn> colors_to_draw;
size_t num_colors_to_draw = 0;

for( auto it = _tags.rbegin(); it != _tags.rend() && num_colors_to_draw < MaxDrawn; ++it ) {
if( it->Color() != utility::Tags::Color::None )
colors_to_draw[num_colors_to_draw++] = it->Color();
}

if( num_colors_to_draw == 0 )
return;

NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
[currentContext saveGraphicsState];

constexpr double radius = static_cast<double>(Diameter / 2);
constexpr double spacing = static_cast<double>(Step);

for( ssize_t i = num_colors_to_draw - 1; i >= 0; --i ) {
auto colors = Color(colors_to_draw[i]);
[colors.first setFill];
if( _accent )
[_accent setStroke];
else
[colors.second setStroke];

NSPoint center = NSMakePoint(_offset_x + i * spacing, _view_height / 2.);

NSBezierPath *circle = [NSBezierPath bezierPath];
[circle appendBezierPathWithArcWithCenter:center radius:radius startAngle:0 endAngle:360];
[circle fill];
[circle setLineWidth:1.];
[circle stroke];

if( i < static_cast<ssize_t>(num_colors_to_draw) - 1 ) {
NSBezierPath *shadow = [NSBezierPath bezierPath];
[shadow appendBezierPathWithArcWithCenter:center radius:radius + 1. startAngle:0 endAngle:360];
[_background setStroke];
[shadow setLineWidth:1.];
[shadow stroke];
}
}

[currentContext restoreGraphicsState];
}

}
49 changes: 49 additions & 0 deletions Source/Panel/tests/UI/TagsPresentation_UT.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) 2024 Michael Kazakov. Subject to GNU General Public License version 3.
#include <sys/dirent.h>
#include <VFS/VFS.h>
#include <VFS/VFSListingInput.h>
#include "UI/TagsPresentation.h"
#include "../Tests.h"

#define PREFIX "TagsPresentation "

using namespace nc;
using namespace nc::base;
using namespace nc::panel;
using utility::Tags;

TEST_CASE(PREFIX "TrailingTagsInplaceDisplay::Place")
{
using Tag = Tags::Tag;
using C = Tags::Color;
constexpr int D = TrailingTagsInplaceDisplay::Diameter;
constexpr int S = TrailingTagsInplaceDisplay::Step;
constexpr int M = TrailingTagsInplaceDisplay::Margin;
const std::string l = "doesnt matter";
struct TC {
std::vector<Tag> tags;
int exp_width = 0;
int exp_margin = 0;
} tcs[] = {
{{}, 0, 0},
{{Tag(&l, C::None)}, 0, 0},
{{Tag(&l, C::Blue)}, D, M},
{{Tag(&l, C::None), Tag(&l, C::None)}, 0, 0},
{{Tag(&l, C::None), Tag(&l, C::None), Tag(&l, C::None)}, 0, 0},
{{Tag(&l, C::None), Tag(&l, C::Blue)}, D, M},
{{Tag(&l, C::Blue), Tag(&l, C::None)}, D, M},
{{Tag(&l, C::None), Tag(&l, C::Blue), Tag(&l, C::None)}, D, M},
{{Tag(&l, C::Blue), Tag(&l, C::Blue)}, D + S, M},
{{Tag(&l, C::Blue), Tag(&l, C::None), Tag(&l, C::Blue)}, D + S, M},
{{Tag(&l, C::None), Tag(&l, C::Blue), Tag(&l, C::Blue)}, D + S, M},
{{Tag(&l, C::Blue), Tag(&l, C::Blue), Tag(&l, C::None)}, D + S, M},
{{Tag(&l, C::None), Tag(&l, C::Blue), Tag(&l, C::Blue), Tag(&l, C::None)}, D + S, M},
{{Tag(&l, C::Blue), Tag(&l, C::Blue), Tag(&l, C::Blue)}, D + S + S, M},
{{Tag(&l, C::Blue), Tag(&l, C::Blue), Tag(&l, C::Blue), Tag(&l, C::Blue)}, D + S + S, M},
{{Tag(&l, C::Blue), Tag(&l, C::None), Tag(&l, C::Blue), Tag(&l, C::Blue), Tag(&l, C::Blue)}, D + S + S, M}};
for( const auto &tc : tcs ) {
auto geom = TrailingTagsInplaceDisplay::Place(tc.tags);
CHECK(geom.width == tc.exp_width);
CHECK(geom.margin == tc.exp_margin);
}
}

0 comments on commit 7467a7d

Please sign in to comment.