Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/helpers/ui_element_helpers.h @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #pragma once | |
| 2 #include <SDK/ui_element.h> | |
| 3 | |
| 4 #ifdef _WIN32 | |
| 5 | |
| 6 // ==================================================================================================== | |
| 7 // ui_element_helpers | |
| 8 // A framework for creating UI Elements that host other elements. | |
| 9 // All foo_ui_std elements that host other elements - such as splitters or tabs - are based on this. | |
| 10 // Note that API 79 (v1.4) or newer is required, earlier did not provide ui_element_common_methods_v3. | |
| 11 // ==================================================================================================== | |
| 12 | |
| 13 #if FOOBAR2000_TARGET_VERSION >= 79 | |
| 14 | |
| 15 #include <memory> | |
| 16 #include <functional> | |
| 17 | |
| 18 namespace ui_element_helpers { | |
| 19 template<typename t_receiver> class ui_element_instance_callback_multi_impl : public ui_element_instance_callback_v3 { | |
| 20 public: | |
| 21 ui_element_instance_callback_multi_impl(t_size id, t_receiver * p_receiver) : m_receiver(p_receiver), m_id(id) {} | |
| 22 void on_min_max_info_change() { | |
| 23 if (m_receiver != NULL) m_receiver->on_min_max_info_change(); | |
| 24 } | |
| 25 bool query_color(const GUID & p_what,t_ui_color & p_out) { | |
| 26 if (m_receiver != NULL) return m_receiver->query_color(p_what,p_out); | |
| 27 else return false; | |
| 28 } | |
| 29 | |
| 30 bool request_activation(service_ptr_t<class ui_element_instance> p_item) { | |
| 31 if (m_receiver) return m_receiver->request_activation(m_id); | |
| 32 else return false; | |
| 33 } | |
| 34 | |
| 35 bool is_edit_mode_enabled() { | |
| 36 if (m_receiver) return m_receiver->is_edit_mode_enabled(); | |
| 37 else return false; | |
| 38 } | |
| 39 void request_replace(service_ptr_t<class ui_element_instance> p_item) { | |
| 40 if (m_receiver) m_receiver->request_replace(m_id); | |
| 41 } | |
| 42 | |
| 43 t_ui_font query_font_ex(const GUID & p_what) { | |
| 44 if (m_receiver) return m_receiver->query_font_ex(p_what); | |
| 45 else return NULL; | |
| 46 } | |
| 47 | |
| 48 t_size notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) { | |
| 49 if (m_receiver) return m_receiver->host_notify(source, what, param1, param2, param2size); | |
| 50 else return 0; | |
| 51 } | |
| 52 void orphan() {m_receiver = NULL;} | |
| 53 | |
| 54 bool is_elem_visible(service_ptr_t<class ui_element_instance> elem) { | |
| 55 if (m_receiver) return m_receiver->is_elem_visible(m_id); | |
| 56 else return false; | |
| 57 } | |
| 58 | |
| 59 void override_id(t_size id) {m_id = id;} | |
| 60 | |
| 61 void on_alt_pressed(bool) {} | |
| 62 private: | |
| 63 t_size m_id; | |
| 64 t_receiver * m_receiver; | |
| 65 }; | |
| 66 class ui_element_instance_callback_receiver_multi { | |
| 67 public: | |
| 68 virtual void on_min_max_info_change() {} | |
| 69 virtual bool query_color(const GUID & p_what,t_ui_color & p_out) {return false;} | |
| 70 virtual bool request_activation(t_size which) {return false;} | |
| 71 virtual bool is_edit_mode_enabled() {return false;} | |
| 72 virtual void request_replace(t_size which) {} | |
| 73 virtual t_ui_font query_font_ex(const GUID&) {return NULL;} | |
| 74 virtual bool is_elem_visible(t_size which) {return true;} | |
| 75 virtual t_size host_notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) {return 0;} | |
| 76 | |
| 77 void ui_element_instance_callback_handle_remove(bit_array const & mask, t_size const oldCount) { | |
| 78 t_callback_list newCallbacks; | |
| 79 t_size newWalk = 0; | |
| 80 for(t_size walk = 0; walk < oldCount; ++walk) { | |
| 81 if (mask[walk]) { | |
| 82 t_callback_ptr ptr; | |
| 83 if (m_callbacks.query(walk,ptr)) { | |
| 84 ptr->orphan(); m_callbacks.remove(walk); | |
| 85 } | |
| 86 } else { | |
| 87 if (newWalk != walk) { | |
| 88 t_callback_ptr ptr; | |
| 89 if (m_callbacks.query(walk,ptr)) { | |
| 90 m_callbacks.remove(walk); | |
| 91 ptr->override_id(newWalk); | |
| 92 m_callbacks.set(newWalk,ptr); | |
| 93 } | |
| 94 } | |
| 95 ++newWalk; | |
| 96 } | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 void ui_element_instance_callback_handle_reorder(const t_size * order, t_size count) { | |
| 101 t_callback_list newCallbacks; | |
| 102 for(t_size walk = 0; walk < count; ++walk) { | |
| 103 t_callback_ptr ptr; | |
| 104 if (m_callbacks.query(order[walk],ptr)) { | |
| 105 ptr->override_id(walk); | |
| 106 newCallbacks.set(walk,ptr); | |
| 107 m_callbacks.remove(order[walk]); | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 PFC_ASSERT( m_callbacks.get_count() == 0 ); | |
| 112 ui_element_instance_callback_release_all(); | |
| 113 | |
| 114 m_callbacks = newCallbacks; | |
| 115 } | |
| 116 | |
| 117 ui_element_instance_callback_ptr ui_element_instance_callback_get_ptr(t_size which) { | |
| 118 t_callback_ptr ptr; | |
| 119 if (!m_callbacks.query(which,ptr)) { | |
| 120 ptr = new service_impl_t<t_callback>(which,this); | |
| 121 m_callbacks.set(which,ptr); | |
| 122 } | |
| 123 return ptr; | |
| 124 } | |
| 125 ui_element_instance_callback_ptr ui_element_instance_callback_create(t_size which) { | |
| 126 ui_element_instance_callback_release(which); | |
| 127 t_callback_ptr ptr = new service_impl_t<t_callback>(which,this); | |
| 128 m_callbacks.set(which,ptr); | |
| 129 return ptr; | |
| 130 } | |
| 131 void ui_element_instance_callback_release_all() { | |
| 132 for(t_callback_list::const_iterator walk = m_callbacks.first(); walk.is_valid(); ++walk) { | |
| 133 walk->m_value->orphan(); | |
| 134 } | |
| 135 m_callbacks.remove_all(); | |
| 136 } | |
| 137 void ui_element_instance_callback_release(t_size which) { | |
| 138 t_callback_ptr ptr; | |
| 139 if (m_callbacks.query(which,ptr)) { | |
| 140 ptr->orphan(); | |
| 141 m_callbacks.remove(which); | |
| 142 } | |
| 143 } | |
| 144 protected: | |
| 145 ~ui_element_instance_callback_receiver_multi() { | |
| 146 ui_element_instance_callback_release_all(); | |
| 147 } | |
| 148 ui_element_instance_callback_receiver_multi() {} | |
| 149 | |
| 150 private: | |
| 151 typedef ui_element_instance_callback_receiver_multi t_self; | |
| 152 typedef ui_element_instance_callback_multi_impl<t_self> t_callback; | |
| 153 typedef service_ptr_t<t_callback> t_callback_ptr; | |
| 154 typedef pfc::map_t<t_size,t_callback_ptr> t_callback_list; | |
| 155 t_callback_list m_callbacks; | |
| 156 }; | |
| 157 | |
| 158 | |
| 159 //! Parses container tree to find configuration of specified element inside a layout configuration. | |
| 160 bool recurse_for_elem_config(ui_element_config::ptr root, ui_element_config::ptr & out, const GUID & toFind); | |
| 161 | |
| 162 ui_element_instance_ptr create_root_container(HWND p_parent,ui_element_instance_callback_ptr p_callback); | |
| 163 ui_element_instance_ptr instantiate(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback); | |
| 164 ui_element_instance_ptr instantiate_dummy(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback); | |
| 165 ui_element_instance_ptr update(ui_element_instance_ptr p_element,HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback); | |
| 166 bool find(service_ptr_t<ui_element> & p_out,const GUID & p_guid); | |
| 167 ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg); | |
| 168 | |
| 169 void replace_with_new_element(ui_element_instance_ptr & p_item,const GUID & p_guid,HWND p_parent,ui_element_instance_callback_ptr p_callback); | |
| 170 | |
| 171 class ui_element_highlight_scope { | |
| 172 public: | |
| 173 ui_element_highlight_scope(HWND wndElem) { | |
| 174 m_highlight = ui_element_common_methods_v3::get()->highlight_element( wndElem ); | |
| 175 } | |
| 176 ~ui_element_highlight_scope() { | |
| 177 DestroyWindow(m_highlight); | |
| 178 } | |
| 179 private: | |
| 180 ui_element_highlight_scope(const ui_element_highlight_scope&) = delete; | |
| 181 void operator=(const ui_element_highlight_scope&) = delete; | |
| 182 HWND m_highlight; | |
| 183 }; | |
| 184 | |
| 185 //! Helper class; provides edit-mode context menu functionality and interacts with "Replace UI Element" dialog. \n | |
| 186 //! Do not use directly - derive from ui_element_instance_host_base instead. | |
| 187 class ui_element_edit_tools { | |
| 188 public: | |
| 189 //! Override me | |
| 190 virtual void host_replace_element(unsigned p_id, ui_element_config::ptr cfg) {} | |
| 191 //! Override me | |
| 192 virtual void host_replace_element(unsigned p_id,const GUID & p_newguid) {} | |
| 193 | |
| 194 //! Override me optionally if you customize edit mode context menu | |
| 195 virtual bool host_edit_mode_context_menu_test(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard) {return false;} | |
| 196 //! Override me optionally if you customize edit mode context menu | |
| 197 virtual void host_edit_mode_context_menu_build(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard,HMENU p_menu,unsigned & p_id_base) {} | |
| 198 //! Override me optionally if you customize edit mode context menu | |
| 199 virtual void host_edit_mode_context_menu_command(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard,unsigned p_id,unsigned p_id_base) {} | |
| 200 //! Override me optionally if you customize edit mode context menu | |
| 201 virtual bool host_edit_mode_context_menu_get_description(unsigned p_childid,unsigned p_id,unsigned p_id_base,pfc::string_base & p_out) {return false;} | |
| 202 | |
| 203 //! Initiates "Replace UI Element" dialog for one of your sub-elements. | |
| 204 void replace_dialog(HWND p_parent,unsigned p_id,const GUID & p_current); | |
| 205 | |
| 206 //! Shows edit mode context menu for your element. | |
| 207 void standard_edit_context_menu(LPARAM p_point,ui_element_instance_ptr p_item,unsigned p_id,HWND p_parent); | |
| 208 | |
| 209 static const char * description_from_menu_command(unsigned p_id); | |
| 210 | |
| 211 bool host_paste_element(unsigned p_id); | |
| 212 | |
| 213 BEGIN_MSG_MAP(ui_element_edit_tools) | |
| 214 MESSAGE_HANDLER(WM_DESTROY,OnDestroy) | |
| 215 END_MSG_MAP() | |
| 216 protected: | |
| 217 ui_element_edit_tools() {} | |
| 218 private: | |
| 219 void on_elem_replace(unsigned p_id,GUID const & newElem); | |
| 220 void _release_replace_dialog(); | |
| 221 LRESULT OnDestroy(UINT,WPARAM,LPARAM,BOOL& bHandled) {bHandled = FALSE; *m_killSwitch = true; _release_replace_dialog(); return 0;} | |
| 222 | |
| 223 CWindow m_replace_dialog; | |
| 224 std::shared_ptr<bool> m_killSwitch = std::make_shared<bool>(); | |
| 225 | |
| 226 ui_element_edit_tools( const ui_element_edit_tools & ) = delete; | |
| 227 void operator=( const ui_element_edit_tools & ) = delete; | |
| 228 }; | |
| 229 | |
| 230 //! Base class for ui_element_instances that host other elements. | |
| 231 class ui_element_instance_host_base : public ui_element_instance, protected ui_element_instance_callback_receiver_multi, protected ui_element_edit_tools { | |
| 232 protected: | |
| 233 // Any derived class must pass their messages to us, by CHAIN_MSG_MAP(ui_element_instance_host_base) | |
| 234 BEGIN_MSG_MAP(ui_element_instance_host_base) | |
| 235 CHAIN_MSG_MAP(ui_element_edit_tools) | |
| 236 MESSAGE_HANDLER(WM_SETTINGCHANGE,OnSettingChange); | |
| 237 END_MSG_MAP() | |
| 238 | |
| 239 //override me | |
| 240 virtual ui_element_instance_ptr host_get_child(t_size which) = 0; | |
| 241 //override me | |
| 242 virtual t_size host_get_children_count() = 0; | |
| 243 //override me (tabs) | |
| 244 virtual void host_bring_to_front(t_size which) {} | |
| 245 //override me | |
| 246 virtual void on_min_max_info_change() {m_callback->on_min_max_info_change();} | |
| 247 //override me | |
| 248 virtual void host_replace_child(t_size which) = 0; | |
| 249 | |
| 250 virtual bool host_is_child_visible(t_size which) {return true;} | |
| 251 | |
| 252 void host_child_visibility_changed(t_size which, bool state) { | |
| 253 if (m_callback->is_elem_visible_(this)) { | |
| 254 ui_element_instance::ptr item = host_get_child(which); | |
| 255 if (item.is_valid()) item->notify(ui_element_notify_visibility_changed,state ? 1 : 0,NULL,0); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 | |
| 260 bool is_elem_visible(t_size which) { | |
| 261 if (!m_callback->is_elem_visible_(this)) return false; | |
| 262 return this->host_is_child_visible(which); | |
| 263 } | |
| 264 | |
| 265 GUID get_subclass() {return ui_element_subclass_containers;} | |
| 266 | |
| 267 double get_focus_priority() { | |
| 268 ui_element_instance_ptr item; double priority; t_size which; | |
| 269 if (!grabTopPriorityVisibleChild(item,which,priority)) return 0; | |
| 270 return priority; | |
| 271 } | |
| 272 void set_default_focus() { | |
| 273 ui_element_instance_ptr item; double priority; t_size which; | |
| 274 if (!grabTopPriorityVisibleChild(item,which,priority)) { | |
| 275 this->set_default_focus_fallback(); | |
| 276 } else { | |
| 277 host_bring_to_front(which); | |
| 278 item->set_default_focus(); | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 bool get_focus_priority_subclass(double & p_out,const GUID & p_subclass) { | |
| 283 ui_element_instance_ptr item; double priority; t_size which; | |
| 284 if (!grabTopPriorityChild(item,which,priority,p_subclass)) return false; | |
| 285 p_out = priority; | |
| 286 return true; | |
| 287 } | |
| 288 bool set_default_focus_subclass(const GUID & p_subclass) { | |
| 289 ui_element_instance_ptr item; double priority; t_size which; | |
| 290 if (!grabTopPriorityChild(item,which,priority,p_subclass)) return false; | |
| 291 host_bring_to_front(which); | |
| 292 return item->set_default_focus_subclass(p_subclass); | |
| 293 } | |
| 294 void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) { | |
| 295 if (p_what == ui_element_notify_visibility_changed) { | |
| 296 const t_size total = host_get_children_count(); | |
| 297 for(t_size walk = 0; walk < total; ++walk) { | |
| 298 if (this->host_is_child_visible(walk)) { | |
| 299 ui_element_instance_ptr item = host_get_child(walk); | |
| 300 if (item.is_valid()) item->notify(p_what,p_param1,p_param2,p_param2size); | |
| 301 } | |
| 302 } | |
| 303 } else if (p_what == ui_element_notify_get_element_labels) { | |
| 304 handleGetLabels(p_param1, p_param2, p_param2size); | |
| 305 } else { | |
| 306 const t_size total = host_get_children_count(); | |
| 307 for(t_size walk = 0; walk < total; ++walk) { | |
| 308 ui_element_instance_ptr item = host_get_child(walk); | |
| 309 if (item.is_valid()) item->notify(p_what,p_param1,p_param2,p_param2size); | |
| 310 } | |
| 311 } | |
| 312 } | |
| 313 bool query_color(const GUID & p_what,t_ui_color & p_out) {return m_callback->query_color(p_what,p_out);} | |
| 314 bool request_activation(t_size which) { | |
| 315 if (!m_callback->request_activation(this)) return false; | |
| 316 host_bring_to_front(which); | |
| 317 return true; | |
| 318 } | |
| 319 bool is_edit_mode_enabled() {return m_callback->is_edit_mode_enabled();} | |
| 320 | |
| 321 t_ui_font query_font_ex(const GUID& id) {return m_callback->query_font_ex(id);} | |
| 322 | |
| 323 void request_replace(t_size which) { | |
| 324 host_replace_child(which); | |
| 325 } | |
| 326 private: | |
| 327 void handleGetLabelsChild(ui_element_instance::ptr child, t_size which, t_size param1, const void * param2, t_size param2size) { | |
| 328 if (child->get_subclass() == ui_element_subclass_containers) { | |
| 329 child->notify(ui_element_notify_get_element_labels, param1, param2, param2size); | |
| 330 } else if (child->get_guid() != pfc::guid_null && child->get_wnd() != NULL && this->host_is_child_visible(which)) { | |
| 331 FB2K_DYNAMIC_ASSERT(param2 != NULL); | |
| 332 reinterpret_cast<ui_element_notify_get_element_labels_callback*>(const_cast<void*>(param2))->set_visible_element(child); | |
| 333 } | |
| 334 } | |
| 335 void handleGetLabels(t_size param1, const void * param2, t_size param2size) { | |
| 336 const t_size childrenTotal = host_get_children_count(); | |
| 337 for(t_size childWalk = 0; childWalk < childrenTotal; ++childWalk) { | |
| 338 ui_element_instance_ptr item = host_get_child(childWalk); | |
| 339 if (item.is_valid()) handleGetLabelsChild(item, childWalk, param1, param2, param2size); | |
| 340 } | |
| 341 } | |
| 342 LRESULT OnSettingChange(UINT msg,WPARAM wp,LPARAM lp,BOOL& bHandled) { | |
| 343 bHandled = FALSE; | |
| 344 const t_size total = host_get_children_count(); | |
| 345 for(t_size walk = 0; walk < total; ++walk) { | |
| 346 ui_element_instance::ptr item = host_get_child(walk); | |
| 347 if (item.is_valid()) { | |
| 348 ::SendMessage(item->get_wnd(),msg,wp,lp); | |
| 349 } | |
| 350 } | |
| 351 return 0; | |
| 352 } | |
| 353 t_size whichChild(ui_element_instance_ptr child) { | |
| 354 const t_size count = host_get_children_count(); | |
| 355 for(t_size walk = 0; walk < count; ++walk) { | |
| 356 if (child == host_get_child(walk)) return walk; | |
| 357 } | |
| 358 return ~0; | |
| 359 } | |
| 360 bool childPriorityCompare(t_size which, double priority, double bestPriority) { | |
| 361 if (host_is_child_visible(which)) return priority >= bestPriority; | |
| 362 else return priority > bestPriority; | |
| 363 } | |
| 364 bool grabTopPriorityVisibleChild(ui_element_instance_ptr & out,t_size & outWhich,double & outPriority); | |
| 365 bool grabTopPriorityChild(ui_element_instance_ptr & out,t_size & outWhich,double & outPriority,const GUID & subclass); | |
| 366 protected: | |
| 367 ui_element_instance_host_base(ui_element_instance_callback::ptr callback) : m_callback(callback) {} | |
| 368 const ui_element_instance_callback::ptr m_callback; | |
| 369 }; | |
| 370 | |
| 371 | |
| 372 bool enforce_min_max_info(CWindow p_window,ui_element_min_max_info const & p_info); | |
| 373 | |
| 374 void handle_WM_GETMINMAXINFO(LPARAM p_lp,const ui_element_min_max_info & p_myinfo); | |
| 375 }; | |
| 376 | |
| 377 void ui_element_instance_standard_context_menu(service_ptr_t<ui_element_instance> p_elem, LPARAM p_pt); | |
| 378 void ui_element_instance_standard_context_menu_eh(service_ptr_t<ui_element_instance> p_elem, LPARAM p_pt); | |
| 379 | |
| 380 #endif // FOOBAR2000_TARGET_VERSION >= 79 | |
| 381 | |
| 382 #endif // _WIN32 |
