Перейти к содержимому

Уроки · 42

Круглые кнопки

Сделаем своё подобие кнопки — просто кружок на экране. Пока кружок никто не трогает, он своего обычного цвета. Как только на него нажали — красится в другой цвет. Отпустили — возвращается обратно.

Для этого нужны сразу две проверки: кнопка мыши зажата (уже знакомо по уроку «Клик мышью»), и мышь при этом находится внутри круга (та же геометрия, что и в уроке «Радиус обнаружения» — расстояние меньше радиуса). Кнопка считается нажатой, только если верно и то, и другое одновременно.

В уроке «Тип bool» мы увидели, что любая проверка — это просто bool. Знак && называется логическое И, и означает он ровно то же самое слово «и» в обычной речи: «и то, и другое». Он берёт два bool и отвечает на один вопрос — верно ли и первое, и второе одновременно:

  • И первое true, и второе true → результат true.
  • Если хотя бы одно из двух — false, результат тоже false, сколько бы условий ни было верных из остальных.
let pressed = mouse_down && inside;

Здесь это работает так: чтобы кнопка считалась нажатой, должны быть верны сразу оба утверждения — и что кнопка мыши зажата прямо сейчас (mouse_down), и что координаты мыши находятся внутри круга (inside). Зажать мышь мимо кнопки — верно только первое, pressed будет false. Навести мышь на кнопку, но не нажимать, — верно только второе, pressed тоже false. Только когда оба условия сходятся одновременно, pressed становится true — в этом и есть логика знака &&, а не просто механическое «слепить два true в один».

Замени содержимое src/main.rs на это:

src/main.rs
use macroquad::prelude::*;
// Один кружок-кнопка: положение, радиус и обычный цвет.
struct Circle {
x: f32,
y: f32,
radius: f32,
color: Color,
}
// Своя функция: расстояние между двумя точками, как в уроке «Расстояние».
fn distance(from_x: f32, from_y: f32, to_x: f32, to_y: f32) -> f32 {
let dx = to_x - from_x;
let dy = to_y - from_y;
(dx * dx + dy * dy).sqrt()
}
#[macroquad::main("Круглые кнопки")]
async fn main() {
loop {
clear_background(BLACK);
let w = screen_width();
let h = screen_height();
// Четыре кнопки в ряд — у каждой своя доля ширины экрана и свой радиус.
let buttons = [
Circle { x: w * 0.2, y: h * 0.5, radius: 140.0, color: YELLOW },
Circle { x: w * 0.4, y: h * 0.5, radius: 200.0, color: RED },
Circle { x: w * 0.6, y: h * 0.5, radius: 160.0, color: GREEN },
Circle { x: w * 0.8, y: h * 0.5, radius: 220.0, color: PURPLE },
];
let (mouse_x, mouse_y) = mouse_position();
// Кнопка мыши зажата прямо сейчас.
let mouse_down = is_mouse_button_down(MouseButton::Left);
for button in &buttons {
// Мышь попадает внутрь именно этого кружка.
let inside = distance(mouse_x, mouse_y, button.x, button.y) < button.radius;
// Кнопка нажата, только если верно и то, и другое сразу.
let pressed = mouse_down && inside;
if pressed {
draw_circle(button.x, button.y, button.radius, BLUE);
} else {
draw_circle(button.x, button.y, button.radius, button.color);
}
}
next_frame().await;
}
}

Запусти как обычно:

Окно терминала
cargo run

Зажми кнопку мыши над любым из четырёх кружков — он посинеет. Отпусти — вернётся к своему цвету. А если зажать кнопку мыши не над кружком — ничего не произойдёт: одной из двух проверок не хватает. Попробуй прямо здесь:

  • w * 0.2, w * 0.4, w * 0.6, w * 0.8 — доли ширины экрана, тот же приём, что и в уроках «Проценты» и «Доля от 0 до 1»: не абсолютные пиксели, а часть от целого, поэтому кнопки остаются на своих местах на любом экране.
  • [Circle { ... }, ...] — фиксированный массив из четырёх кружков: их ровно четыре, известно заранее, и это не меняется, — тот самый случай, когда массив уместнее Vec (как в уроке «Разноцветные кружки»).
  • inside — bool, попадает ли мышь в конкретный кружок; mouse_down — bool, зажата ли кнопка мыши. Оба уже встречались по отдельности раньше.
  • mouse_down && inside — оба должны быть true одновременно, иначе pressed окажется false. Мышь может быть зажата где угодно на экране — кнопка среагирует, только если это ещё и внутри неё.
  • for button in &buttons проверяет и рисует каждую кнопку одним и тем же кодом — не важно, что цвета у них разные.
  • Радиус у каждой кнопки тоже свой, не один общий на все четыре — именно поэтому он хранится как поле radius в Circle, а не отдельной константой снаружи: у каждого значения buttons он собственный.