/* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2019-2020, William McPherson * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include "KeysWidget.h" #include "TrackManager.h" #include #include #include KeysWidget::KeysWidget(TrackManager& track_manager) : m_track_manager(track_manager) { set_fill_with_background_color(true); } int KeysWidget::mouse_note() const { if (m_mouse_down && m_mouse_note + m_track_manager.octave_base() < note_count) return m_mouse_note; // Can be -1. else return -1; } void KeysWidget::set_key(int key, Switch switch_key) { if (key == -1 || key + m_track_manager.octave_base() >= note_count) return; if (switch_key == On) { ++m_key_on[key]; } else { if (m_key_on[key] >= 1) --m_key_on[key]; } VERIFY(m_key_on[key] <= 2); m_track_manager.set_keyboard_note(key + m_track_manager.octave_base(), switch_key); } bool KeysWidget::note_is_set(int note) const { if (note < m_track_manager.octave_base()) return false; if (note >= m_track_manager.octave_base() + note_count) return false; return m_key_on[note - m_track_manager.octave_base()] != 0; } int KeysWidget::key_code_to_key(int key_code) const { switch (key_code) { case Key_A: return 0; case Key_W: return 1; case Key_S: return 2; case Key_E: return 3; case Key_D: return 4; case Key_F: return 5; case Key_T: return 6; case Key_G: return 7; case Key_Y: return 8; case Key_H: return 9; case Key_U: return 10; case Key_J: return 11; case Key_K: return 12; case Key_O: return 13; case Key_L: return 14; case Key_P: return 15; case Key_Semicolon: return 16; case Key_Apostrophe: return 17; case Key_RightBracket: return 18; case Key_Return: return 19; default: return -1; } } constexpr int white_key_width = 24; constexpr int black_key_width = 16; constexpr int black_key_x_offset = black_key_width / 2; constexpr int black_key_height = 60; constexpr int white_key_labels_count = 12; constexpr Array white_key_labels = { "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "\'", "\u23CE", // Return key symbol }; constexpr int black_key_labels_count = 8; constexpr Array black_key_labels = { "W", "E", "T", "Y", "U", "O", "P", "]", }; constexpr int black_key_offsets[] = { white_key_width, white_key_width * 2, white_key_width, white_key_width, white_key_width * 2, }; constexpr int white_key_note_accumulator[] = { 2, 2, 1, 2, 2, 2, 1, }; constexpr int black_key_note_accumulator[] = { 2, 3, 2, 2, 3, }; void KeysWidget::paint_event(GUI::PaintEvent& event) { GUI::Painter painter(*this); painter.translate(frame_thickness(), frame_thickness()); int note = 0; int x = 0; int i = 0; for (;;) { Gfx::IntRect rect(x, 0, white_key_width, frame_inner_rect().height()); painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::White); painter.draw_rect(rect, Color::Black); if (i < white_key_labels_count) { rect.set_height(rect.height() * 1.5); painter.draw_text(rect, white_key_labels[i], Gfx::TextAlignment::Center, Color::Black); } note += white_key_note_accumulator[i % white_keys_per_octave]; x += white_key_width; ++i; if (note + m_track_manager.octave_base() >= note_count) break; if (x >= frame_inner_rect().width()) break; } note = 1; x = white_key_width - black_key_x_offset; i = 0; for (;;) { Gfx::IntRect rect(x, 0, black_key_width, black_key_height); painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::Black); painter.draw_rect(rect, Color::Black); if (i < black_key_labels_count) { rect.set_height(rect.height() * 1.5); painter.draw_text(rect, black_key_labels[i], Gfx::TextAlignment::Center, Color::White); } note += black_key_note_accumulator[i % black_keys_per_octave]; x += black_key_offsets[i % black_keys_per_octave]; ++i; if (note + m_track_manager.octave_base() >= note_count) break; if (x >= frame_inner_rect().width()) break; } GUI::Frame::paint_event(event); } constexpr int notes_per_white_key[] = { 1, 3, 5, 6, 8, 10, 12, }; // Keep in mind that in any of these functions a note value can be out of // bounds. Bounds checking is done in set_key(). static inline int note_from_white_keys(int white_keys) { int octaves = white_keys / white_keys_per_octave; int remainder = white_keys % white_keys_per_octave; int notes_from_octaves = octaves * notes_per_octave; int notes_from_remainder = notes_per_white_key[remainder]; int note = (notes_from_octaves + notes_from_remainder) - 1; return note; } int KeysWidget::note_for_event_position(const Gfx::IntPoint& a_point) const { if (!frame_inner_rect().contains(a_point)) return -1; auto point = a_point; point.translate_by(-frame_thickness(), -frame_thickness()); int white_keys = point.x() / white_key_width; int note = note_from_white_keys(white_keys); bool black_key_on_left = note != 0 && key_pattern[(note - 1) % notes_per_octave] == Black; if (black_key_on_left) { int black_key_x = (white_keys * white_key_width) - black_key_x_offset; Gfx::IntRect black_key(black_key_x, 0, black_key_width, black_key_height); if (black_key.contains(point)) return note - 1; } bool black_key_on_right = key_pattern[(note + 1) % notes_per_octave] == Black; if (black_key_on_right) { int black_key_x = ((white_keys + 1) * white_key_width) - black_key_x_offset; Gfx::IntRect black_key(black_key_x, 0, black_key_width, black_key_height); if (black_key.contains(point)) return note + 1; } return note; } void KeysWidget::mousedown_event(GUI::MouseEvent& event) { if (event.button() != GUI::MouseButton::Primary) return; m_mouse_down = true; m_mouse_note = note_for_event_position(event.position()); set_key(m_mouse_note, On); update(); } void KeysWidget::mouseup_event(GUI::MouseEvent& event) { if (event.button() != GUI::MouseButton::Primary) return; m_mouse_down = false; set_key(m_mouse_note, Off); update(); } void KeysWidget::mousemove_event(GUI::MouseEvent& event) { if (!m_mouse_down) return; int new_mouse_note = note_for_event_position(event.position()); if (m_mouse_note == new_mouse_note) return; set_key(m_mouse_note, Off); set_key(new_mouse_note, On); update(); m_mouse_note = new_mouse_note; }