egui_charts/drawings/
repositories.rs1use crate::drawings::DrawingToolType;
10use crate::drawings::categories;
11
12pub trait DrawingToolsRepository {
17 fn get_all_categories(&self) -> Vec<String>;
19
20 fn get_category_tools(&self, category: &str) -> Vec<(String, Vec<DrawingToolType>)>;
24
25 fn get_tool_category(&self, tool: DrawingToolType) -> Option<String>;
27
28 fn search_tools(&self, query: &str) -> Vec<DrawingToolType>;
30}
31
32pub struct InMemoryToolsRepository;
34
35impl InMemoryToolsRepository {
36 pub fn new() -> Self {
38 Self
39 }
40}
41
42impl Default for InMemoryToolsRepository {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl DrawingToolsRepository for InMemoryToolsRepository {
49 fn get_all_categories(&self) -> Vec<String> {
50 categories::CATEGORY_ORDER
51 .iter()
52 .map(|category| category.to_string())
53 .collect()
54 }
55
56 fn get_category_tools(&self, category: &str) -> Vec<(String, Vec<DrawingToolType>)> {
57 categories::get_category_sections(category)
58 .into_iter()
59 .map(|(section, tools)| (section.to_string(), tools))
60 .collect()
61 }
62
63 fn get_tool_category(&self, tool: DrawingToolType) -> Option<String> {
64 categories::get_tool_category(tool).map(|s| s.to_string())
65 }
66
67 fn search_tools(&self, query: &str) -> Vec<DrawingToolType> {
68 if query.is_empty() {
69 return self.get_all_tools();
70 }
71
72 let query_lower = query.to_lowercase();
73 self.get_all_tools()
74 .into_iter()
75 .filter(|tool| tool.as_str().to_lowercase().contains(&query_lower))
76 .collect()
77 }
78}
79
80impl InMemoryToolsRepository {
81 fn get_all_tools(&self) -> Vec<DrawingToolType> {
83 let mut all_tools = Vec::new();
84 for category in self.get_all_categories() {
85 for (_, tools) in self.get_category_tools(&category) {
86 all_tools.extend(tools);
87 }
88 }
89 all_tools
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
100 fn tools_are_found_by_category_and_search() {
101 let repo = InMemoryToolsRepository::new();
102 assert_eq!(repo.get_all_categories(), categories::CATEGORY_ORDER);
103 assert_eq!(categories::get_category_default_tool("Nonsense"), None);
104 assert!(DrawingToolType::by_category("Nonsense").is_empty());
105 let cases = [
106 (DrawingToolType::CrossCursor, "Cursors", "Cursors", "cross"),
107 (DrawingToolType::TrendLine, "Lines", "Lines", "trend"),
108 (
109 DrawingToolType::FibonacciRetracement,
110 "Fibonacci",
111 "Fibonacci",
112 "FIB",
113 ),
114 (
115 DrawingToolType::Table,
116 "Text/Annotations",
117 "Annotations",
118 "table",
119 ),
120 (
121 DrawingToolType::Image,
122 "Text/Annotations",
123 "Content",
124 "image",
125 ),
126 (
127 DrawingToolType::Tweet,
128 "Text/Annotations",
129 "Content",
130 "tweet",
131 ),
132 (DrawingToolType::Idea, "Text/Annotations", "Content", "idea"),
133 (
134 DrawingToolType::GhostFeed,
135 "Projection",
136 "Projection",
137 "ghost",
138 ),
139 ];
140 for (tool, category, command_category, query) in cases {
141 assert_eq!(
142 repo.get_tool_category(tool).as_deref(),
143 Some(category),
144 "{tool:?}"
145 );
146 assert!(
147 DrawingToolType::by_category(command_category).contains(&tool),
148 "{tool:?} missing from command search"
149 );
150 let listed = repo
151 .get_category_tools(category)
152 .iter()
153 .any(|(_, tools)| tools.contains(&tool));
154 assert!(listed, "{tool:?} is not listed under {category}");
155 assert!(
156 repo.search_tools(query).contains(&tool),
157 "search {query:?} misses {tool:?}"
158 );
159 }
160 }
161}