forked from Bananymous/banan-os
				
			LibGUI: Implement very bad widget system :D
This commit is contained in:
		
							parent
							
								
									4695fa061d
								
							
						
					
					
						commit
						d73a667437
					
				|  | @ -1,5 +1,11 @@ | ||||||
| set(LIBGUI_SOURCES | set(LIBGUI_SOURCES | ||||||
| 	Texture.cpp | 	Texture.cpp | ||||||
|  | 	Widget/Button.cpp | ||||||
|  | 	Widget/Grid.cpp | ||||||
|  | 	Widget/Label.cpp | ||||||
|  | 	Widget/RoundedWidget.cpp | ||||||
|  | 	Widget/TextArea.cpp | ||||||
|  | 	Widget/Widget.cpp | ||||||
| 	Window.cpp | 	Window.cpp | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | #include <LibFont/Font.h> | ||||||
|  | #include <LibGUI/Widget/Button.h> | ||||||
|  | #include <LibGUI/Window.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<BAN::RefPtr<Button>> Button::create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry) | ||||||
|  | 	{ | ||||||
|  | 		auto* button_ptr = new Button(parent, geometry); | ||||||
|  | 		if (button_ptr == nullptr) | ||||||
|  | 			return BAN::Error::from_errno(ENOMEM); | ||||||
|  | 		auto button = BAN::RefPtr<Button>::adopt(button_ptr); | ||||||
|  | 		TRY(button->initialize(color_invisible)); | ||||||
|  | 		TRY(button->m_text.append(text)); | ||||||
|  | 		return button; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Button::set_text(BAN::StringView text) | ||||||
|  | 	{ | ||||||
|  | 		m_text.clear(); | ||||||
|  | 		TRY(m_text.append(text)); | ||||||
|  | 		if (is_shown()) | ||||||
|  | 			show(); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Button::update_impl() | ||||||
|  | 	{ | ||||||
|  | 		const bool hover_color = is_hovered() && !is_child_hovered(); | ||||||
|  | 		if (hover_color != m_hover_state) | ||||||
|  | 			show(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Button::show_impl() | ||||||
|  | 	{ | ||||||
|  | 		m_hover_state = is_hovered() && !is_child_hovered(); | ||||||
|  | 
 | ||||||
|  | 		const auto& font = default_font(); | ||||||
|  | 		const int32_t text_h = font.height(); | ||||||
|  | 		const int32_t text_w = font.width() * m_text.size(); | ||||||
|  | 
 | ||||||
|  | 		const int32_t off_x = (static_cast<int32_t>(width())  - text_w) / 2; | ||||||
|  | 		const int32_t off_y = (static_cast<int32_t>(height()) - text_h) / 2; | ||||||
|  | 
 | ||||||
|  | 		m_texture.fill(m_hover_state ? m_style.color_hovered : m_style.color_normal); | ||||||
|  | 		m_texture.draw_text(m_text, font, off_x, off_y, m_style.color_text); | ||||||
|  | 
 | ||||||
|  | 		RoundedWidget::style() = m_style; | ||||||
|  | 		RoundedWidget::show_impl(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bool Button::on_mouse_button_impl(LibGUI::EventPacket::MouseButtonEvent::event_t event) | ||||||
|  | 	{ | ||||||
|  | 		if (event.pressed && event.button == LibInput::MouseButton::Left && m_click_callback) | ||||||
|  | 			m_click_callback(); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | #include <LibGUI/Widget/Grid.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<BAN::RefPtr<Grid>> Grid::create(BAN::RefPtr<Widget> parent, uint32_t cols, uint32_t rows, uint32_t color, Rectangle geometry) | ||||||
|  | 	{ | ||||||
|  | 		auto* grid_ptr = new Grid(parent, geometry, cols, rows); | ||||||
|  | 		if (grid_ptr == nullptr) | ||||||
|  | 			return BAN::Error::from_errno(ENOMEM); | ||||||
|  | 		auto grid = BAN::RefPtr<Grid>::adopt(grid_ptr); | ||||||
|  | 		TRY(grid->initialize(color)); | ||||||
|  | 		return grid; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Widget::Rectangle Grid::grid_element_area(const GridElement& element) const | ||||||
|  | 	{ | ||||||
|  | 		const uint32_t this_x = element.col * width()  / m_cols; | ||||||
|  | 		const uint32_t this_y = element.row * height() / m_rows; | ||||||
|  | 
 | ||||||
|  | 		const uint32_t next_x = (element.col + element.col_span) * width() / m_cols; | ||||||
|  | 		const uint32_t next_y = (element.row + element.row_span) * height() / m_rows; | ||||||
|  | 
 | ||||||
|  | 		return Widget::Rectangle { | ||||||
|  | 			.x = static_cast<int32_t>(this_x), | ||||||
|  | 			.y = static_cast<int32_t>(this_y), | ||||||
|  | 			.w = next_x - this_x, | ||||||
|  | 			.h = next_y - this_y, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Grid::update_geometry_impl() | ||||||
|  | 	{ | ||||||
|  | 		for (auto& grid_element : m_grid_elements) | ||||||
|  | 			TRY(grid_element.widget->set_fixed_geometry(grid_element_area(grid_element))); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Grid::set_widget_position(BAN::RefPtr<Widget> widget, uint32_t col, uint32_t col_span, uint32_t row, uint32_t row_span) | ||||||
|  | 	{ | ||||||
|  | 		if (col_span == 0 || row_span == 0) | ||||||
|  | 			return BAN::Error::from_errno(EINVAL); | ||||||
|  | 		if (col + col_span > m_cols) | ||||||
|  | 			return BAN::Error::from_errno(EINVAL); | ||||||
|  | 		if (row + row_span > m_rows) | ||||||
|  | 			return BAN::Error::from_errno(EINVAL); | ||||||
|  | 		ASSERT(widget->parent() == this); | ||||||
|  | 		TRY(m_grid_elements.emplace_back(widget, col, col_span, row, row_span)); | ||||||
|  | 		TRY(widget->set_fixed_geometry(grid_element_area(m_grid_elements.back()))); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | #include <LibFont/Font.h> | ||||||
|  | #include <LibGUI/Widget/Label.h> | ||||||
|  | #include <LibGUI/Window.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<BAN::RefPtr<Label>> Label::create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry) | ||||||
|  | 	{ | ||||||
|  | 		auto* label_ptr = new Label(parent, geometry); | ||||||
|  | 		if (label_ptr == nullptr) | ||||||
|  | 			return BAN::Error::from_errno(ENOMEM); | ||||||
|  | 		auto label = BAN::RefPtr<Label>::adopt(label_ptr); | ||||||
|  | 		TRY(label->initialize(color_invisible)); | ||||||
|  | 		TRY(label->m_text.append(text)); | ||||||
|  | 		return label; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Label::set_text(BAN::StringView text) | ||||||
|  | 	{ | ||||||
|  | 		m_text.clear(); | ||||||
|  | 		TRY(m_text.append(text)); | ||||||
|  | 		if (is_shown()) | ||||||
|  | 			show(); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Label::show_impl() | ||||||
|  | 	{ | ||||||
|  | 		const auto& font = default_font(); | ||||||
|  | 		const int32_t text_h = font.height(); | ||||||
|  | 		const int32_t text_w = font.width() * m_text.size(); | ||||||
|  | 
 | ||||||
|  | 		const int32_t off_x = (static_cast<int32_t>(width())  - text_w) / 2; | ||||||
|  | 		const int32_t off_y = (static_cast<int32_t>(height()) - text_h) / 2; | ||||||
|  | 
 | ||||||
|  | 		m_texture.fill(m_style.color_normal); | ||||||
|  | 		m_texture.draw_text(m_text, font, off_x, off_y, m_style.color_text); | ||||||
|  | 
 | ||||||
|  | 		RoundedWidget::style() = m_style; | ||||||
|  | 		RoundedWidget::show_impl(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,117 @@ | ||||||
|  | #include <LibGUI/Widget/RoundedWidget.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	bool RoundedWidget::contains(Point point) const | ||||||
|  | 	{ | ||||||
|  | 		if (!Widget::contains(point)) | ||||||
|  | 			return false; | ||||||
|  | 
 | ||||||
|  | 		const auto is_outside_corner = | ||||||
|  | 			[this]<uint8_t quadrant>(Point point) -> bool | ||||||
|  | 			{ | ||||||
|  | 				const auto radius = m_style.corner_radius; | ||||||
|  | 				const uint32_t base_x = (quadrant % 2) ? m_texture.width()  - radius - 1 : 0; | ||||||
|  | 				const uint32_t base_y = (quadrant / 2) ? m_texture.height() - radius - 1 : 0; | ||||||
|  | 
 | ||||||
|  | 				if (point.x < static_cast<int32_t>(base_x) || point.x > static_cast<int32_t>(base_x + radius)) | ||||||
|  | 					return false; | ||||||
|  | 				if (point.y < static_cast<int32_t>(base_y) || point.y > static_cast<int32_t>(base_y + radius)) | ||||||
|  | 					return false; | ||||||
|  | 
 | ||||||
|  | 				const uint32_t x_off = point.x - base_x; | ||||||
|  | 				const uint32_t y_off = point.y - base_y; | ||||||
|  | 
 | ||||||
|  | 				const uint32_t dx = ((quadrant % 2) ? x_off : radius - x_off); | ||||||
|  | 				const uint32_t dy = ((quadrant / 2) ? y_off : radius - y_off); | ||||||
|  | 				const uint32_t distance = dx * dx + dy * dy; | ||||||
|  | 
 | ||||||
|  | 				return distance >= radius * radius; | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 		if (is_outside_corner.operator()<0>(point)) | ||||||
|  | 			return false; | ||||||
|  | 		if (is_outside_corner.operator()<1>(point)) | ||||||
|  | 			return false; | ||||||
|  | 		if (is_outside_corner.operator()<2>(point)) | ||||||
|  | 			return false; | ||||||
|  | 		if (is_outside_corner.operator()<3>(point)) | ||||||
|  | 			return false; | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void RoundedWidget::show_impl() | ||||||
|  | 	{ | ||||||
|  | 		if (m_style.border_width) | ||||||
|  | 		{ | ||||||
|  | 			m_texture.fill_rect( | ||||||
|  | 				0, | ||||||
|  | 				0, | ||||||
|  | 				m_texture.width(), | ||||||
|  | 				m_style.border_width, | ||||||
|  | 				m_style.color_border | ||||||
|  | 			); | ||||||
|  | 			m_texture.fill_rect( | ||||||
|  | 				0, | ||||||
|  | 				0, | ||||||
|  | 				m_style.border_width, | ||||||
|  | 				m_texture.height(), | ||||||
|  | 				m_style.color_border | ||||||
|  | 			); | ||||||
|  | 			m_texture.fill_rect( | ||||||
|  | 				0, | ||||||
|  | 				m_texture.height() - m_style.border_width, | ||||||
|  | 				m_texture.width(), | ||||||
|  | 				m_style.border_width, | ||||||
|  | 				m_style.color_border | ||||||
|  | 			); | ||||||
|  | 			m_texture.fill_rect( | ||||||
|  | 				m_texture.width() - m_style.border_width, | ||||||
|  | 				0, | ||||||
|  | 				m_style.border_width, | ||||||
|  | 				m_texture.height(), | ||||||
|  | 				m_style.color_border | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (m_style.corner_radius) | ||||||
|  | 		{ | ||||||
|  | 			const auto draw_corner = | ||||||
|  | 				[this]<uint8_t quadrant>() | ||||||
|  | 				{ | ||||||
|  | 					const auto radius = m_style.corner_radius; | ||||||
|  | 					const uint32_t base_x = (quadrant % 2) ? m_texture.width()  - radius - 1 : 0; | ||||||
|  | 					const uint32_t base_y = (quadrant / 2) ? m_texture.height() - radius - 1 : 0; | ||||||
|  | 					const uint32_t distance_max = radius * radius; | ||||||
|  | 					const uint32_t distance_min = (radius - m_style.border_width) * (radius - m_style.border_width); | ||||||
|  | 
 | ||||||
|  | 					for (uint32_t y_off = 0; y_off <= radius; y_off++) | ||||||
|  | 					{ | ||||||
|  | 						for (uint32_t x_off = 0; x_off <= radius; x_off++) | ||||||
|  | 						{ | ||||||
|  | 							const uint32_t dx = ((quadrant % 2) ? x_off : radius - x_off); | ||||||
|  | 							const uint32_t dy = ((quadrant / 2) ? y_off : radius - y_off); | ||||||
|  | 							const uint32_t distance = dx * dx + dy * dy; | ||||||
|  | 
 | ||||||
|  | 							if (base_x + x_off >= m_texture.width()) | ||||||
|  | 								continue; | ||||||
|  | 							if (base_y + y_off >= m_texture.height()) | ||||||
|  | 								continue; | ||||||
|  | 
 | ||||||
|  | 							if (distance > distance_max) | ||||||
|  | 								m_texture.set_pixel(base_x + x_off, base_y + y_off, color_invisible); | ||||||
|  | 							else if (distance >= distance_min) | ||||||
|  | 								m_texture.set_pixel(base_x + x_off, base_y + y_off, m_style.color_border); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				}; | ||||||
|  | 
 | ||||||
|  | 			draw_corner.operator()<0>(); | ||||||
|  | 			draw_corner.operator()<1>(); | ||||||
|  | 			draw_corner.operator()<2>(); | ||||||
|  | 			draw_corner.operator()<3>(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,96 @@ | ||||||
|  | #include <LibFont/Font.h> | ||||||
|  | #include <LibGUI/Widget/TextArea.h> | ||||||
|  | #include <LibGUI/Window.h> | ||||||
|  | 
 | ||||||
|  | #include <ctype.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<BAN::RefPtr<TextArea>> TextArea::create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry) | ||||||
|  | 	{ | ||||||
|  | 		auto* text_area_ptr = new TextArea(parent, geometry); | ||||||
|  | 		if (text_area_ptr == nullptr) | ||||||
|  | 			return BAN::Error::from_errno(ENOMEM); | ||||||
|  | 		auto text_area = BAN::RefPtr<TextArea>::adopt(text_area_ptr); | ||||||
|  | 		TRY(text_area->initialize(color_invisible)); | ||||||
|  | 		TRY(text_area->set_text(text)); | ||||||
|  | 		return text_area; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> TextArea::set_text(BAN::StringView text) | ||||||
|  | 	{ | ||||||
|  | 		m_text.clear(); | ||||||
|  | 		TRY(m_text.append(text)); | ||||||
|  | 		TRY(wrap_text()); | ||||||
|  | 		if (is_shown()) | ||||||
|  | 			show(); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	uint32_t TextArea::get_required_height() const | ||||||
|  | 	{ | ||||||
|  | 		auto& font = default_font(); | ||||||
|  | 		return m_wrapped_text.size() * font.height(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> TextArea::wrap_text() | ||||||
|  | 	{ | ||||||
|  | 		m_wrapped_text.clear(); | ||||||
|  | 
 | ||||||
|  | 		if (width() == 0) | ||||||
|  | 			return {}; | ||||||
|  | 
 | ||||||
|  | 		auto& font = default_font(); | ||||||
|  | 
 | ||||||
|  | 		const uint32_t total_columns = width() / font.width(); | ||||||
|  | 		ASSERT(total_columns != 0); | ||||||
|  | 
 | ||||||
|  | 		TRY(m_wrapped_text.emplace_back()); | ||||||
|  | 
 | ||||||
|  | 		for (size_t i = 0; i < m_text.size(); i++) | ||||||
|  | 		{ | ||||||
|  | 			if (m_text[i] == '\n') | ||||||
|  | 				TRY(m_wrapped_text.emplace_back()); | ||||||
|  | 			else if (isspace(m_text[i]) && m_wrapped_text.back().size() == 0) | ||||||
|  | 				; | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				TRY(m_wrapped_text.back().push_back(m_text[i])); | ||||||
|  | 
 | ||||||
|  | 				if (i + 1 < m_text.size() && isspace(m_text[i]) && !isspace(m_text[i + 1])) | ||||||
|  | 				{ | ||||||
|  | 					size_t word_len = 0; | ||||||
|  | 					for (size_t j = i + 1; j < m_text.size() && !isspace(m_text[j]); j++) | ||||||
|  | 						word_len++; | ||||||
|  | 					if (word_len <= total_columns && m_wrapped_text.back().size() + word_len > total_columns) | ||||||
|  | 						TRY(m_wrapped_text.emplace_back()); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (m_wrapped_text.back().size() >= total_columns) | ||||||
|  | 					TRY(m_wrapped_text.emplace_back()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> TextArea::update_geometry_impl() | ||||||
|  | 	{ | ||||||
|  | 		TRY(wrap_text()); | ||||||
|  | 		return Widget::update_geometry_impl(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void TextArea::show_impl() | ||||||
|  | 	{ | ||||||
|  | 		const auto& font = default_font(); | ||||||
|  | 
 | ||||||
|  | 		m_texture.fill(m_style.color_normal); | ||||||
|  | 		for (int32_t row = 0; row < static_cast<int32_t>(m_wrapped_text.size()); row++) | ||||||
|  | 			m_texture.draw_text(m_wrapped_text[row].sv(), font, 0, row * font.height(), m_style.color_text); | ||||||
|  | 
 | ||||||
|  | 		RoundedWidget::style() = m_style; | ||||||
|  | 		RoundedWidget::show_impl(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,241 @@ | ||||||
|  | #include <LibGUI/Widget/Widget.h> | ||||||
|  | #include <LibFont/Font.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	static BAN::Optional<LibFont::Font> s_default_font; | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Widget::set_default_font(BAN::StringView path) | ||||||
|  | 	{ | ||||||
|  | 		s_default_font = TRY(LibFont::Font::load(path)); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const LibFont::Font& Widget::default_font() | ||||||
|  | 	{ | ||||||
|  | 		if (!s_default_font.has_value()) | ||||||
|  | 			MUST(set_default_font("/usr/share/fonts/lat0-16.psfu"_sv)); | ||||||
|  | 		return s_default_font.value(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<BAN::RefPtr<Widget>> Widget::create(BAN::RefPtr<Widget> parent, uint32_t color, Rectangle area) | ||||||
|  | 	{ | ||||||
|  | 		auto* widget_ptr = new Widget(parent, area); | ||||||
|  | 		if (widget_ptr == nullptr) | ||||||
|  | 			return BAN::Error::from_errno(ENOMEM); | ||||||
|  | 		auto widget = BAN::RefPtr<Widget>::adopt(widget_ptr); | ||||||
|  | 		TRY(widget->initialize(color)); | ||||||
|  | 		return widget; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Widget::initialize(uint32_t color) | ||||||
|  | 	{ | ||||||
|  | 		m_texture = TRY(Texture::create(width(), height(), color)); | ||||||
|  | 		if (m_parent) | ||||||
|  | 			TRY(m_parent->m_children.push_back(this)); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bool Widget::is_child_hovered() const | ||||||
|  | 	{ | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 			if (child->m_hovered || child->is_child_hovered()) | ||||||
|  | 				return true; | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Widget::set_fixed_geometry(Rectangle area) | ||||||
|  | 	{ | ||||||
|  | 		TRY(m_texture.resize(area.w, area.h)); | ||||||
|  | 		m_fixed_area = area; | ||||||
|  | 		m_relative_area.clear(); | ||||||
|  | 
 | ||||||
|  | 		TRY(update_geometry_impl()); | ||||||
|  | 
 | ||||||
|  | 		if (is_shown()) | ||||||
|  | 			show(); | ||||||
|  | 
 | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Widget::set_relative_geometry(FloatRectangle area) | ||||||
|  | 	{ | ||||||
|  | 		if (area.w < 0.0f || area.h < 0.0f) | ||||||
|  | 			return BAN::Error::from_errno(EINVAL); | ||||||
|  | 		ASSERT(m_parent); | ||||||
|  | 
 | ||||||
|  | 		TRY(set_fixed_geometry({ | ||||||
|  | 			.x = static_cast<int32_t>(area.x * m_parent->width()), | ||||||
|  | 			.y = static_cast<int32_t>(area.y * m_parent->height()), | ||||||
|  | 			.w = static_cast<uint32_t>(area.w * m_parent->width()), | ||||||
|  | 			.h = static_cast<uint32_t>(area.h * m_parent->height()), | ||||||
|  | 		})); | ||||||
|  | 
 | ||||||
|  | 		m_relative_area = area; | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	BAN::ErrorOr<void> Widget::update_geometry_impl() | ||||||
|  | 	{ | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 		{ | ||||||
|  | 			if (!child->m_relative_area.has_value()) | ||||||
|  | 				continue; | ||||||
|  | 			TRY(child->set_relative_geometry(child->m_relative_area.value())); | ||||||
|  | 		} | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Widget::show() | ||||||
|  | 	{ | ||||||
|  | 		m_shown = true; | ||||||
|  | 
 | ||||||
|  | 		show_impl(); | ||||||
|  | 		m_changed = true; | ||||||
|  | 
 | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 			if (child->is_shown()) | ||||||
|  | 				child->show(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Widget::hide() | ||||||
|  | 	{ | ||||||
|  | 		if (!is_shown()) | ||||||
|  | 			return; | ||||||
|  | 		m_shown = false; | ||||||
|  | 
 | ||||||
|  | 		auto root = m_parent; | ||||||
|  | 		while (root && root->m_parent) | ||||||
|  | 			root = root->m_parent; | ||||||
|  | 		if (root) | ||||||
|  | 			root->show(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Widget::before_mouse_move() | ||||||
|  | 	{ | ||||||
|  | 		if (!is_shown()) | ||||||
|  | 			return; | ||||||
|  | 
 | ||||||
|  | 		m_old_hovered = m_hovered; | ||||||
|  | 		m_hovered = false; | ||||||
|  | 
 | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 			child->before_mouse_move(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Widget::after_mouse_move() | ||||||
|  | 	{ | ||||||
|  | 		if (!is_shown()) | ||||||
|  | 			return; | ||||||
|  | 
 | ||||||
|  | 		if (m_old_hovered != m_hovered) | ||||||
|  | 			on_hover_change_impl(m_hovered); | ||||||
|  | 
 | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 			child->after_mouse_move(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bool Widget::on_mouse_move(LibGUI::EventPacket::MouseMoveEvent::event_t event) | ||||||
|  | 	{ | ||||||
|  | 		if (!Rectangle { 0, 0, width(), height() }.contains({ event.x, event.y })) | ||||||
|  | 			return false; | ||||||
|  | 		if (!is_shown()) | ||||||
|  | 			return false; | ||||||
|  | 
 | ||||||
|  | 		m_hovered = contains({ event.x, event.y }); | ||||||
|  | 
 | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 		{ | ||||||
|  | 			auto rel_event = event; | ||||||
|  | 			rel_event.x -= child->m_fixed_area.x; | ||||||
|  | 			rel_event.y -= child->m_fixed_area.y; | ||||||
|  | 			if (child->on_mouse_move(rel_event)) | ||||||
|  | 				return true; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!m_hovered) | ||||||
|  | 			return false; | ||||||
|  | 		return on_mouse_move_impl(event); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bool Widget::on_mouse_button(LibGUI::EventPacket::MouseButtonEvent::event_t event) | ||||||
|  | 	{ | ||||||
|  | 		if (!Rectangle { 0, 0, width(), height() }.contains({ event.x, event.y })) | ||||||
|  | 			return false; | ||||||
|  | 		if (!is_shown()) | ||||||
|  | 			return false; | ||||||
|  | 
 | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 		{ | ||||||
|  | 			auto rel_event = event; | ||||||
|  | 			rel_event.x -= child->m_fixed_area.x; | ||||||
|  | 			rel_event.y -= child->m_fixed_area.y; | ||||||
|  | 			if (child->on_mouse_button(rel_event)) | ||||||
|  | 				return true; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!contains({ event.x, event.y })) | ||||||
|  | 			return false; | ||||||
|  | 		return on_mouse_button_impl(event); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Widget::Rectangle Widget::render(Texture& output, Point parent_position, Rectangle out_area) | ||||||
|  | 	{ | ||||||
|  | 		if (!is_shown()) | ||||||
|  | 			return {}; | ||||||
|  | 
 | ||||||
|  | 		update_impl(); | ||||||
|  | 
 | ||||||
|  | 		Rectangle invalidated {}; | ||||||
|  | 		if (m_changed) | ||||||
|  | 		{ | ||||||
|  | 			auto where_i_would_be = Rectangle { | ||||||
|  | 				.x = parent_position.x + m_fixed_area.x, | ||||||
|  | 				.y = parent_position.y + m_fixed_area.y, | ||||||
|  | 				.w = m_fixed_area.w, | ||||||
|  | 				.h = m_fixed_area.h, | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			auto where_to_draw = out_area.overlap(where_i_would_be); | ||||||
|  | 
 | ||||||
|  | 			if (where_to_draw.w && where_to_draw.h) | ||||||
|  | 			{ | ||||||
|  | 				output.copy_texture( | ||||||
|  | 					m_texture, | ||||||
|  | 					where_to_draw.x, | ||||||
|  | 					where_to_draw.y, | ||||||
|  | 					where_to_draw.x - where_i_would_be.x, | ||||||
|  | 					where_to_draw.y - where_i_would_be.y, | ||||||
|  | 					where_to_draw.w, | ||||||
|  | 					where_to_draw.h | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			invalidated = where_to_draw; | ||||||
|  | 			m_changed = false; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		out_area = out_area.overlap({ | ||||||
|  | 			.x = parent_position.x + m_fixed_area.x, | ||||||
|  | 			.y = parent_position.y + m_fixed_area.y, | ||||||
|  | 			.w = m_fixed_area.w, | ||||||
|  | 			.h = m_fixed_area.h, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		parent_position = { | ||||||
|  | 			.x = parent_position.x + m_fixed_area.x, | ||||||
|  | 			.y = parent_position.y + m_fixed_area.y, | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		for (auto child : m_children) | ||||||
|  | 		{ | ||||||
|  | 			invalidated = invalidated.bounding_box( | ||||||
|  | 				child->render(output, parent_position, out_area) | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return invalidated; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -113,6 +113,17 @@ namespace LibGUI | ||||||
| 		return window; | 		return window; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	BAN::ErrorOr<void> Window::set_root_widget(BAN::RefPtr<Widget::Widget> widget) | ||||||
|  | 	{ | ||||||
|  | 		TRY(widget->set_fixed_geometry({ 0, 0, m_width, m_height })); | ||||||
|  | 		m_root_widget = widget; | ||||||
|  | 		m_root_widget->show(); | ||||||
|  | 		const auto invalidated = m_root_widget->render(m_texture, { 0, 0 }, { 0, 0, m_width, m_height }); | ||||||
|  | 		if (invalidated.w && invalidated.h) | ||||||
|  | 			invalidate(invalidated.x, invalidated.y, invalidated.w, invalidated.h); | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	void Window::invalidate(int32_t x, int32_t y, uint32_t width, uint32_t height) | 	void Window::invalidate(int32_t x, int32_t y, uint32_t width, uint32_t height) | ||||||
| 	{ | 	{ | ||||||
| 		if (!m_texture.clamp_to_texture(x, y, width, height)) | 		if (!m_texture.clamp_to_texture(x, y, width, height)) | ||||||
|  | @ -168,6 +179,28 @@ namespace LibGUI | ||||||
| 			return on_socket_error(__FUNCTION__); | 			return on_socket_error(__FUNCTION__); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	void Window::set_cursor_visible(bool visible) | ||||||
|  | 	{ | ||||||
|  | 		auto attributes = m_attributes; | ||||||
|  | 		if (attributes.cursor_visible == visible) | ||||||
|  | 			return; | ||||||
|  | 		attributes.cursor_visible = visible; | ||||||
|  | 		set_attributes(attributes); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void Window::set_cursor(uint32_t width, uint32_t height, BAN::Span<uint32_t> pixels) | ||||||
|  | 	{ | ||||||
|  | 		WindowPacket::WindowSetCursor packet; | ||||||
|  | 		packet.width = width; | ||||||
|  | 		packet.height = height; | ||||||
|  | 		MUST(packet.pixels.resize(pixels.size())); | ||||||
|  | 		for (size_t i = 0; i < packet.pixels.size(); i++) | ||||||
|  | 			packet.pixels[i] = pixels[i]; | ||||||
|  | 
 | ||||||
|  | 		if (auto ret = packet.send_serialized(m_server_fd); ret.is_error()) | ||||||
|  | 			return on_socket_error(__FUNCTION__); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	void Window::set_min_size(uint32_t width, uint32_t height) | 	void Window::set_min_size(uint32_t width, uint32_t height) | ||||||
| 	{ | 	{ | ||||||
| 		WindowPacket::WindowSetMinSize packet; | 		WindowPacket::WindowSetMinSize packet; | ||||||
|  | @ -238,6 +271,9 @@ namespace LibGUI | ||||||
| 
 | 
 | ||||||
| 		TRY(m_texture.resize(event.width, event.height)); | 		TRY(m_texture.resize(event.width, event.height)); | ||||||
| 
 | 
 | ||||||
|  | 		if (m_root_widget) | ||||||
|  | 			TRY(m_root_widget->set_fixed_geometry({ 0, 0, event.width, event.height })); | ||||||
|  | 
 | ||||||
| 		void* framebuffer_addr = smo_map(event.smo_key); | 		void* framebuffer_addr = smo_map(event.smo_key); | ||||||
| 		if (framebuffer_addr == nullptr) | 		if (framebuffer_addr == nullptr) | ||||||
| 			return BAN::Error::from_errno(errno); | 			return BAN::Error::from_errno(errno); | ||||||
|  | @ -304,13 +340,27 @@ namespace LibGUI | ||||||
| 						m_key_event_callback(TRY_OR_BREAK(EventPacket::KeyEvent::deserialize(packet_data.span())).event); | 						m_key_event_callback(TRY_OR_BREAK(EventPacket::KeyEvent::deserialize(packet_data.span())).event); | ||||||
| 					break; | 					break; | ||||||
| 				case PacketType::MouseButtonEvent: | 				case PacketType::MouseButtonEvent: | ||||||
|  | 				{ | ||||||
|  | 					auto event = TRY_OR_BREAK(EventPacket::MouseButtonEvent::deserialize(packet_data.span())).event; | ||||||
| 					if (m_mouse_button_event_callback) | 					if (m_mouse_button_event_callback) | ||||||
| 						m_mouse_button_event_callback(TRY_OR_BREAK(EventPacket::MouseButtonEvent::deserialize(packet_data.span())).event); | 						m_mouse_button_event_callback(event); | ||||||
|  | 					if (m_root_widget) | ||||||
|  | 						m_root_widget->on_mouse_button(event); | ||||||
| 					break; | 					break; | ||||||
|  | 				} | ||||||
| 				case PacketType::MouseMoveEvent: | 				case PacketType::MouseMoveEvent: | ||||||
|  | 				{ | ||||||
|  | 					auto event = TRY_OR_BREAK(EventPacket::MouseMoveEvent::deserialize(packet_data.span())).event; | ||||||
| 					if (m_mouse_move_event_callback) | 					if (m_mouse_move_event_callback) | ||||||
| 						m_mouse_move_event_callback(TRY_OR_BREAK(EventPacket::MouseMoveEvent::deserialize(packet_data.span())).event); | 						m_mouse_move_event_callback(event); | ||||||
|  | 					if (m_root_widget) | ||||||
|  | 					{ | ||||||
|  | 						m_root_widget->before_mouse_move(); | ||||||
|  | 						m_root_widget->on_mouse_move(event); | ||||||
|  | 						m_root_widget->after_mouse_move(); | ||||||
|  | 					} | ||||||
| 					break; | 					break; | ||||||
|  | 				} | ||||||
| 				case PacketType::MouseScrollEvent: | 				case PacketType::MouseScrollEvent: | ||||||
| 					if (m_mouse_scroll_event_callback) | 					if (m_mouse_scroll_event_callback) | ||||||
| 						m_mouse_scroll_event_callback(TRY_OR_BREAK(EventPacket::MouseScrollEvent::deserialize(packet_data.span())).event); | 						m_mouse_scroll_event_callback(TRY_OR_BREAK(EventPacket::MouseScrollEvent::deserialize(packet_data.span())).event); | ||||||
|  | @ -320,6 +370,13 @@ namespace LibGUI | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| #undef TRY_OR_BREAK | #undef TRY_OR_BREAK | ||||||
|  | 
 | ||||||
|  | 		if (m_root_widget) | ||||||
|  | 		{ | ||||||
|  | 			const auto invalidated = m_root_widget->render(m_texture, { 0, 0 }, { 0, 0, m_width, m_height }); | ||||||
|  | 			if (invalidated.w && invalidated.h) | ||||||
|  | 				invalidate(invalidated.x, invalidated.y, invalidated.w, invalidated.h); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,54 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <BAN/Function.h> | ||||||
|  | #include <BAN/StringView.h> | ||||||
|  | 
 | ||||||
|  | #include <LibGUI/Widget/RoundedWidget.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	class Button : public RoundedWidget | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		struct Style : RoundedWidget::Style | ||||||
|  | 		{ | ||||||
|  | 			Style() | ||||||
|  | 				: RoundedWidget::Style() | ||||||
|  | 				, color_hovered(0x808080) | ||||||
|  | 				, color_text(0x000000) | ||||||
|  | 			{} | ||||||
|  | 
 | ||||||
|  | 			uint32_t color_hovered; | ||||||
|  | 			uint32_t color_text; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 	public: | ||||||
|  | 		static BAN::ErrorOr<BAN::RefPtr<Button>> create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry = {}); | ||||||
|  | 
 | ||||||
|  | 		BAN::ErrorOr<void> set_text(BAN::StringView); | ||||||
|  | 
 | ||||||
|  | 		Style& style() { return m_style; } | ||||||
|  | 		const Style& style() const { return m_style; } | ||||||
|  | 
 | ||||||
|  | 		void set_click_callback(BAN::Function<void()> callback) { m_click_callback = callback; } | ||||||
|  | 
 | ||||||
|  | 	protected: | ||||||
|  | 		Button(BAN::RefPtr<Widget> parent, Rectangle area) | ||||||
|  | 			: RoundedWidget(parent, area) | ||||||
|  | 		{ } | ||||||
|  | 
 | ||||||
|  | 		void update_impl() override; | ||||||
|  | 		void show_impl() override; | ||||||
|  | 
 | ||||||
|  | 		bool on_mouse_button_impl(LibGUI::EventPacket::MouseButtonEvent::event_t) override; | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		Style m_style; | ||||||
|  | 		bool m_hover_state { false }; | ||||||
|  | 		BAN::String m_text; | ||||||
|  | 
 | ||||||
|  | 		BAN::Function<void()> m_click_callback; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <LibGUI/Widget/Widget.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	class Grid : public Widget | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		static BAN::ErrorOr<BAN::RefPtr<Grid>> create(BAN::RefPtr<Widget> parent, uint32_t cols, uint32_t rows, uint32_t color = color_invisible, Rectangle geometry = {}); | ||||||
|  | 
 | ||||||
|  | 		BAN::ErrorOr<void> set_widget_position(BAN::RefPtr<Widget> widget, uint32_t col, uint32_t col_span, uint32_t row, uint32_t row_span); | ||||||
|  | 
 | ||||||
|  | 	protected: | ||||||
|  | 		Grid(BAN::RefPtr<Widget> parent, Rectangle geometry, uint32_t cols, uint32_t rows) | ||||||
|  | 			: Widget(parent, geometry) | ||||||
|  | 			, m_cols(cols) | ||||||
|  | 			, m_rows(rows) | ||||||
|  | 		{ } | ||||||
|  | 
 | ||||||
|  | 		BAN::ErrorOr<void> update_geometry_impl() override; | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		struct GridElement | ||||||
|  | 		{ | ||||||
|  | 			BAN::RefPtr<Widget> widget; | ||||||
|  | 			uint32_t col; | ||||||
|  | 			uint32_t col_span; | ||||||
|  | 			uint32_t row; | ||||||
|  | 			uint32_t row_span; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		Rectangle grid_element_area(const GridElement& element) const; | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		const uint32_t m_cols; | ||||||
|  | 		const uint32_t m_rows; | ||||||
|  | 		BAN::Vector<GridElement> m_grid_elements; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <BAN/StringView.h> | ||||||
|  | 
 | ||||||
|  | #include <LibGUI/Widget/RoundedWidget.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	class Label : public RoundedWidget | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		struct Style : RoundedWidget::Style | ||||||
|  | 		{ | ||||||
|  | 			Style() | ||||||
|  | 				: RoundedWidget::Style() | ||||||
|  | 				, color_text(0x000000) | ||||||
|  | 			{} | ||||||
|  | 
 | ||||||
|  | 			uint32_t color_text; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 	public: | ||||||
|  | 		static BAN::ErrorOr<BAN::RefPtr<Label>> create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry = {}); | ||||||
|  | 
 | ||||||
|  | 		BAN::StringView text() const { return m_text; } | ||||||
|  | 		BAN::ErrorOr<void> set_text(BAN::StringView); | ||||||
|  | 
 | ||||||
|  | 		Style& style() { return m_style; } | ||||||
|  | 		const Style& style() const { return m_style; } | ||||||
|  | 
 | ||||||
|  | 	protected: | ||||||
|  | 		Label(BAN::RefPtr<Widget> parent, Rectangle area) | ||||||
|  | 			: RoundedWidget(parent, area) | ||||||
|  | 		{ } | ||||||
|  | 
 | ||||||
|  | 		void show_impl() override; | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		Style m_style; | ||||||
|  | 		BAN::String m_text; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <LibGUI/Widget/Widget.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	class RoundedWidget : public Widget | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		struct Style | ||||||
|  | 		{ | ||||||
|  | 			Style(uint32_t color_normal = 0xA0A0A0, uint32_t border_width = 1, uint32_t color_border = 0x000000, uint32_t corner_radius = 5) | ||||||
|  | 				: color_normal(color_normal) | ||||||
|  | 				, border_width(border_width) | ||||||
|  | 				, color_border(color_border) | ||||||
|  | 				, corner_radius(corner_radius) | ||||||
|  | 			{} | ||||||
|  | 
 | ||||||
|  | 			uint32_t color_normal; | ||||||
|  | 			uint32_t border_width; | ||||||
|  | 			uint32_t color_border; | ||||||
|  | 			uint32_t corner_radius; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		Style& style() { return m_style; } | ||||||
|  | 		const Style& style() const { return m_style; } | ||||||
|  | 
 | ||||||
|  | 	protected: | ||||||
|  | 		RoundedWidget(BAN::RefPtr<Widget> parent, Rectangle area) | ||||||
|  | 			: Widget(parent, area) | ||||||
|  | 		{ } | ||||||
|  | 
 | ||||||
|  | 		bool contains(Point point) const override; | ||||||
|  | 
 | ||||||
|  | 		void show_impl() override; | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		Style m_style; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,50 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <BAN/StringView.h> | ||||||
|  | 
 | ||||||
|  | #include <LibGUI/Widget/RoundedWidget.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	class TextArea : public RoundedWidget | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		struct Style : RoundedWidget::Style | ||||||
|  | 		{ | ||||||
|  | 			Style() | ||||||
|  | 				: RoundedWidget::Style() | ||||||
|  | 				, color_text(0x000000) | ||||||
|  | 			{} | ||||||
|  | 
 | ||||||
|  | 			uint32_t color_text; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 	public: | ||||||
|  | 		static BAN::ErrorOr<BAN::RefPtr<TextArea>> create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry = {}); | ||||||
|  | 
 | ||||||
|  | 		BAN::StringView text() const { return m_text; } | ||||||
|  | 		BAN::ErrorOr<void> set_text(BAN::StringView); | ||||||
|  | 
 | ||||||
|  | 		uint32_t get_required_height() const; | ||||||
|  | 
 | ||||||
|  | 		Style& style() { return m_style; } | ||||||
|  | 		const Style& style() const { return m_style; } | ||||||
|  | 
 | ||||||
|  | 	protected: | ||||||
|  | 		TextArea(BAN::RefPtr<Widget> parent, Rectangle area) | ||||||
|  | 			: RoundedWidget(parent, area) | ||||||
|  | 		{ } | ||||||
|  | 
 | ||||||
|  | 		BAN::ErrorOr<void> wrap_text(); | ||||||
|  | 
 | ||||||
|  | 		BAN::ErrorOr<void> update_geometry_impl() override; | ||||||
|  | 		void show_impl() override; | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		Style m_style; | ||||||
|  | 		BAN::String m_text; | ||||||
|  | 		BAN::Vector<BAN::String> m_wrapped_text; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,163 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <BAN/RefPtr.h> | ||||||
|  | 
 | ||||||
|  | #include <LibGUI/Texture.h> | ||||||
|  | #include <LibGUI/Packet.h> | ||||||
|  | 
 | ||||||
|  | namespace LibGUI { class Window; } | ||||||
|  | 
 | ||||||
|  | namespace LibGUI::Widget | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	class Widget : public BAN::RefCounted<Widget> | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		static constexpr uint32_t color_invisible = Texture::color_invisible; | ||||||
|  | 
 | ||||||
|  | 		struct Point | ||||||
|  | 		{ | ||||||
|  | 			int32_t x, y; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		struct FloatRectangle | ||||||
|  | 		{ | ||||||
|  | 			float x, y; | ||||||
|  | 			float w, h; | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		struct Rectangle | ||||||
|  | 		{ | ||||||
|  | 			int32_t x, y; | ||||||
|  | 			uint32_t w, h; | ||||||
|  | 
 | ||||||
|  | 			struct Bounds | ||||||
|  | 			{ | ||||||
|  | 				int32_t min_x, min_y; | ||||||
|  | 				int32_t max_x, max_y; | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			bool contains(Point point) const | ||||||
|  | 			{ | ||||||
|  | 				if (point.x < x || point.x >= x + static_cast<int32_t>(w)) | ||||||
|  | 					return false; | ||||||
|  | 				if (point.y < y || point.y >= y + static_cast<int32_t>(h)) | ||||||
|  | 					return false; | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			Bounds bounds(Rectangle other) const | ||||||
|  | 			{ | ||||||
|  | 				return Bounds { | ||||||
|  | 					.min_x = BAN::Math::max(x, other.x), | ||||||
|  | 					.min_y = BAN::Math::max(y, other.y), | ||||||
|  | 					.max_x = BAN::Math::min(x + static_cast<int32_t>(w), other.x + static_cast<int32_t>(other.w)), | ||||||
|  | 					.max_y = BAN::Math::min(y + static_cast<int32_t>(h), other.y + static_cast<int32_t>(other.h)), | ||||||
|  | 				}; | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			Rectangle overlap(Rectangle other) const | ||||||
|  | 			{ | ||||||
|  | 				const auto min_x = BAN::Math::max(x, other.x); | ||||||
|  | 				const auto min_y = BAN::Math::max(y, other.y); | ||||||
|  | 				const auto max_x = BAN::Math::min(x + static_cast<int32_t>(w), other.x + static_cast<int32_t>(other.w)); | ||||||
|  | 				const auto max_y = BAN::Math::min(y + static_cast<int32_t>(h), other.y + static_cast<int32_t>(other.h)); | ||||||
|  | 				if (min_x >= max_x || min_y >= max_y) | ||||||
|  | 					return {}; | ||||||
|  | 				return Rectangle { | ||||||
|  | 					.x = min_x, | ||||||
|  | 					.y = min_y, | ||||||
|  | 					.w = static_cast<uint32_t>(max_x - min_x), | ||||||
|  | 					.h = static_cast<uint32_t>(max_y - min_y), | ||||||
|  | 				}; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			Rectangle bounding_box(Rectangle other) const | ||||||
|  | 			{ | ||||||
|  | 				if (w == 0 || h == 0) | ||||||
|  | 					return other; | ||||||
|  | 				if (other.w == 0 || other.h == 0) | ||||||
|  | 					return *this; | ||||||
|  | 				const auto min_x = BAN::Math::min(x, other.x); | ||||||
|  | 				const auto min_y = BAN::Math::min(y, other.y); | ||||||
|  | 				const auto max_x = BAN::Math::max(x + static_cast<int32_t>(w), other.x + static_cast<int32_t>(other.w)); | ||||||
|  | 				const auto max_y = BAN::Math::max(y + static_cast<int32_t>(h), other.y + static_cast<int32_t>(other.h)); | ||||||
|  | 				return Rectangle { | ||||||
|  | 					.x = min_x, | ||||||
|  | 					.y = min_y, | ||||||
|  | 					.w = static_cast<uint32_t>(max_x - min_x), | ||||||
|  | 					.h = static_cast<uint32_t>(max_y - min_y), | ||||||
|  | 				}; | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 	public: | ||||||
|  | 		static BAN::ErrorOr<BAN::RefPtr<Widget>> create(BAN::RefPtr<Widget> parent, uint32_t color = color_invisible, Rectangle geometry = {}); | ||||||
|  | 
 | ||||||
|  | 		static BAN::ErrorOr<void> set_default_font(BAN::StringView path); | ||||||
|  | 		static const LibFont::Font& default_font(); | ||||||
|  | 
 | ||||||
|  | 		void show(); | ||||||
|  | 		void hide(); | ||||||
|  | 
 | ||||||
|  | 		BAN::ErrorOr<void> set_fixed_geometry(Rectangle); | ||||||
|  | 		BAN::ErrorOr<void> set_relative_geometry(FloatRectangle); | ||||||
|  | 
 | ||||||
|  | 		BAN::RefPtr<Widget> parent() { return m_parent; } | ||||||
|  | 
 | ||||||
|  | 		uint32_t width() const { return m_fixed_area.w; } | ||||||
|  | 		uint32_t height() const { return m_fixed_area.h; } | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		void before_mouse_move(); | ||||||
|  | 		void after_mouse_move(); | ||||||
|  | 		bool on_mouse_move(LibGUI::EventPacket::MouseMoveEvent::event_t); | ||||||
|  | 
 | ||||||
|  | 		bool on_mouse_button(LibGUI::EventPacket::MouseButtonEvent::event_t); | ||||||
|  | 
 | ||||||
|  | 	protected: | ||||||
|  | 		Widget(BAN::RefPtr<Widget> parent, Rectangle area) | ||||||
|  | 			: m_parent(parent) | ||||||
|  | 			, m_fixed_area(area) | ||||||
|  | 		{ } | ||||||
|  | 
 | ||||||
|  | 		BAN::ErrorOr<void> initialize(uint32_t color); | ||||||
|  | 
 | ||||||
|  | 		virtual bool contains(Point point) const { return Rectangle { 0, 0, width(), height() }.contains(point); } | ||||||
|  | 
 | ||||||
|  | 		bool is_hovered() const { return m_hovered; } | ||||||
|  | 		bool is_child_hovered() const; | ||||||
|  | 
 | ||||||
|  | 		bool is_shown() const { return m_shown; } | ||||||
|  | 
 | ||||||
|  | 		Rectangle render(Texture& output, Point parent_position, Rectangle out_area); | ||||||
|  | 
 | ||||||
|  | 		virtual void update_impl() {} | ||||||
|  | 		virtual void show_impl() {} | ||||||
|  | 
 | ||||||
|  | 		virtual BAN::ErrorOr<void> update_geometry_impl(); | ||||||
|  | 
 | ||||||
|  | 		virtual void on_hover_change_impl(bool hovered) { (void)hovered; } | ||||||
|  | 		virtual bool on_mouse_move_impl(LibGUI::EventPacket::MouseMoveEvent::event_t) { return true; } | ||||||
|  | 		virtual bool on_mouse_button_impl(LibGUI::EventPacket::MouseButtonEvent::event_t) { return true; } | ||||||
|  | 
 | ||||||
|  | 	protected: | ||||||
|  | 		Texture m_texture; | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		BAN::RefPtr<Widget> m_parent; | ||||||
|  | 		BAN::Vector<BAN::RefPtr<Widget>> m_children; | ||||||
|  | 		bool m_shown { false }; | ||||||
|  | 
 | ||||||
|  | 		Rectangle m_fixed_area; | ||||||
|  | 		BAN::Optional<FloatRectangle> m_relative_area; | ||||||
|  | 
 | ||||||
|  | 		bool m_changed { false }; | ||||||
|  | 
 | ||||||
|  | 		bool m_hovered { false }; | ||||||
|  | 		bool m_old_hovered { false }; | ||||||
|  | 
 | ||||||
|  | 		friend class LibGUI::Window; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <LibGUI/Packet.h> | #include <LibGUI/Packet.h> | ||||||
| #include <LibGUI/Texture.h> | #include <LibGUI/Texture.h> | ||||||
|  | #include <LibGUI/Widget/Widget.h> | ||||||
| 
 | 
 | ||||||
| namespace LibFont { class Font; } | namespace LibFont { class Font; } | ||||||
| 
 | 
 | ||||||
|  | @ -32,6 +33,9 @@ namespace LibGUI | ||||||
| 
 | 
 | ||||||
| 		static BAN::ErrorOr<BAN::UniqPtr<Window>> create(uint32_t width, uint32_t height, BAN::StringView title, Attributes attributes = default_attributes); | 		static BAN::ErrorOr<BAN::UniqPtr<Window>> create(uint32_t width, uint32_t height, BAN::StringView title, Attributes attributes = default_attributes); | ||||||
| 
 | 
 | ||||||
|  | 		BAN::ErrorOr<void> set_root_widget(BAN::RefPtr<Widget::Widget> widget); | ||||||
|  | 		BAN::RefPtr<Widget::Widget> root_widget() { return m_root_widget; } | ||||||
|  | 
 | ||||||
| 		Texture& texture() { return m_texture; } | 		Texture& texture() { return m_texture; } | ||||||
| 		const Texture& texture() const { return m_texture; } | 		const Texture& texture() const { return m_texture; } | ||||||
| 
 | 
 | ||||||
|  | @ -94,6 +98,7 @@ namespace LibGUI | ||||||
| 		uint32_t m_height { 0 }; | 		uint32_t m_height { 0 }; | ||||||
| 
 | 
 | ||||||
| 		Texture m_texture; | 		Texture m_texture; | ||||||
|  | 		BAN::RefPtr<Widget::Widget> m_root_widget; | ||||||
| 
 | 
 | ||||||
| 		BAN::Function<void()>                                       m_socket_error_callback; | 		BAN::Function<void()>                                       m_socket_error_callback; | ||||||
| 		BAN::Function<void()>                                       m_close_window_event_callback; | 		BAN::Function<void()>                                       m_close_window_event_callback; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue