#include #include #include #include #include #include #include #include WSMenuManager::WSMenuManager() { m_audio_client = make(); m_audio_client->on_muted_state_change = [this](bool muted) { if (m_audio_muted == muted) return; m_audio_muted = muted; if (m_window) { draw(); m_window->invalidate(); } }; m_unmuted_bitmap = GraphicsBitmap::load_from_file("/res/icons/audio-unmuted.png"); m_muted_bitmap = GraphicsBitmap::load_from_file("/res/icons/audio-muted.png"); m_username = getlogin(); m_needs_window_resize = false; m_timer = CTimer::construct(300, [this] { static time_t last_update_time; time_t now = time(nullptr); if (now != last_update_time || m_cpu_monitor.is_dirty()) { tick_clock(); last_update_time = now; m_cpu_monitor.set_dirty(false); } }); } WSMenuManager::~WSMenuManager() { } void WSMenuManager::setup() { m_window = WSWindow::construct(*this, WSWindowType::Menubar); m_window->set_rect(WSWindowManager::the().menubar_rect()); } bool WSMenuManager::is_open(const WSMenu& menu) const { for (int i = 0; i < m_open_menu_stack.size(); ++i) { if (&menu == m_open_menu_stack[i].ptr()) return true; } return false; } void WSMenuManager::draw() { auto& wm = WSWindowManager::the(); auto menubar_rect = wm.menubar_rect(); if (m_needs_window_resize) { m_window->set_rect(menubar_rect); m_needs_window_resize = false; } Painter painter(*window().backing_store()); painter.fill_rect(menubar_rect, Color::WarmGray); painter.draw_line({ 0, menubar_rect.bottom() }, { menubar_rect.right(), menubar_rect.bottom() }, Color::MidGray); int index = 0; wm.for_each_active_menubar_menu([&](WSMenu& menu) { Color text_color = Color::Black; if (is_open(menu)) { painter.fill_rect(menu.rect_in_menubar(), Color::from_rgb(0xad714f)); painter.draw_rect(menu.rect_in_menubar(), Color::from_rgb(0x793016)); text_color = Color::White; } painter.draw_text( menu.text_rect_in_menubar(), menu.name(), index == 1 ? wm.app_menu_font() : wm.menu_font(), TextAlignment::CenterLeft, text_color); ++index; return true; }); int username_width = Font::default_bold_font().width(m_username); // FIXME: This rect should only be computed once. Rect username_rect { menubar_rect.right() - wm.menubar_menu_margin() / 2 - Font::default_bold_font().width(m_username), menubar_rect.y(), username_width, menubar_rect.height() }; painter.draw_text(username_rect, m_username, Font::default_bold_font(), TextAlignment::CenterRight, Color::Black); time_t now = time(nullptr); auto* tm = localtime(&now); auto time_text = String::format("%4u-%02u-%02u %02u:%02u:%02u", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); int time_width = wm.font().width(time_text); // FIXME: This rect should only be computed once. Rect time_rect { username_rect.left() - wm.menubar_menu_margin() / 2 - time_width, menubar_rect.y(), time_width, menubar_rect.height() }; painter.draw_text(time_rect, time_text, wm.font(), TextAlignment::CenterRight, Color::Black); // FIXME: This rect should only be computed once. Rect cpu_rect { time_rect.right() - wm.font().width(time_text) - m_cpu_monitor.capacity() - 10, time_rect.y() + 1, m_cpu_monitor.capacity(), time_rect.height() - 2 }; m_cpu_monitor.paint(painter, cpu_rect); // FIXME: This rect should only be computed once. m_audio_rect = { cpu_rect.left() - 20, cpu_rect.y(), 12, 16 }; auto& audio_bitmap = m_audio_muted ? *m_muted_bitmap : *m_unmuted_bitmap; painter.blit(m_audio_rect.location(), audio_bitmap, audio_bitmap.rect()); } void WSMenuManager::tick_clock() { refresh(); } void WSMenuManager::refresh() { if (!m_window) return; draw(); window().invalidate(); } void WSMenuManager::event(CEvent& event) { if (WSWindowManager::the().active_window_is_modal()) return CObject::event(event); if (event.type() == WSEvent::MouseMove || event.type() == WSEvent::MouseUp || event.type() == WSEvent::MouseDown || event.type() == WSEvent::MouseWheel) { auto& mouse_event = static_cast(event); WSWindowManager::the().for_each_active_menubar_menu([&](WSMenu& menu) { if (menu.rect_in_menubar().contains(mouse_event.position())) { handle_menu_mouse_event(menu, mouse_event); return false; } return true; }); if (mouse_event.type() == WSEvent::MouseDown && mouse_event.button() == MouseButton::Left && m_audio_rect.contains(mouse_event.position())) { m_audio_client->set_muted(!m_audio_muted); draw(); m_window->invalidate(); } } return CObject::event(event); } void WSMenuManager::handle_menu_mouse_event(WSMenu& menu, const WSMouseEvent& event) { auto& wm = WSWindowManager::the(); bool is_hover_with_any_menu_open = event.type() == WSMouseEvent::MouseMove && !m_open_menu_stack.is_empty() && (m_open_menu_stack.first()->menubar() || m_open_menu_stack.first() == wm.system_menu()); bool is_mousedown_with_left_button = event.type() == WSMouseEvent::MouseDown && event.button() == MouseButton::Left; bool should_open_menu = &menu != m_current_menu && (is_hover_with_any_menu_open || is_mousedown_with_left_button); if (is_mousedown_with_left_button) m_bar_open = !m_bar_open; if (should_open_menu && m_bar_open) { if (m_current_menu == &menu) return; close_everyone(); if (!menu.is_empty()) { auto& menu_window = menu.ensure_menu_window(); menu_window.move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() + 2 }); menu_window.set_visible(true); } set_current_menu(&menu); refresh(); return; } if (!m_bar_open) close_everyone(); } void WSMenuManager::set_needs_window_resize() { m_needs_window_resize = true; } void WSMenuManager::close_everyone() { for (auto& menu : m_open_menu_stack) { if (menu && menu->menu_window()) menu->menu_window()->set_visible(false); } m_open_menu_stack.clear(); m_current_menu = nullptr; refresh(); } void WSMenuManager::close_everyone_not_in_lineage(WSMenu& menu) { Vector menus_to_close; for (auto& open_menu : m_open_menu_stack) { if (!open_menu) continue; if (&menu == open_menu.ptr() || open_menu->is_menu_ancestor_of(menu)) continue; menus_to_close.append(open_menu); } close_menus(menus_to_close); } void WSMenuManager::close_menus(const Vector& menus) { for (auto& menu : menus) { if (menu == m_current_menu) m_current_menu = nullptr; if (menu->menu_window()) menu->menu_window()->set_visible(false); m_open_menu_stack.remove_first_matching([&](auto& entry) { return entry == menu; }); } refresh(); } static void collect_menu_subtree(WSMenu& menu, Vector& menus) { menus.append(&menu); for (int i = 0; i < menu.item_count(); ++i) { auto& item = menu.item(i); if (!item.is_submenu()) continue; collect_menu_subtree(*const_cast(item).submenu(), menus); } } void WSMenuManager::close_menu_and_descendants(WSMenu& menu) { Vector menus_to_close; collect_menu_subtree(menu, menus_to_close); close_menus(menus_to_close); } void WSMenuManager::set_current_menu(WSMenu* menu, bool is_submenu) { if (!is_submenu && m_current_menu) m_current_menu->close(); if (menu) m_current_menu = menu->make_weak_ptr(); if (!is_submenu) { close_everyone(); if (menu) m_open_menu_stack.append(menu->make_weak_ptr()); } else { m_open_menu_stack.append(menu->make_weak_ptr()); } } void WSMenuManager::close_bar() { close_everyone(); m_bar_open = false; }