/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/std/algorithm.h" #include "ags/shared/gui/gui_listbox.h" #include "ags/shared/ac/game_version.h" #include "ags/shared/font/fonts.h" #include "ags/shared/gui/gui_main.h" #include "ags/shared/util/stream.h" #include "ags/shared/util/string_utils.h" namespace AGS3 { namespace AGS { namespace Shared { GUIListBox::GUIListBox() { ItemCount = 0; SelectedItem = 0; TopItem = 0; RowHeight = 0; VisibleItemCount = 0; Font = 0; TextColor = 0; SelectedTextColor = 7; ListBoxFlags = kListBox_DefFlags; SelectedBgColor = 16; TextAlignment = kHAlignLeft; _scEventCount = 1; _scEventNames[0] = "SelectionChanged"; _scEventArgs[0] = "GUIControl *control"; } bool GUIListBox::HasAlphaChannel() const { return is_font_antialiased(Font); } int GUIListBox::GetItemAt(int x, int y) const { if (RowHeight <= 0 || IsInRightMargin(x)) return -1; int index = y / RowHeight + TopItem; if (index < 0 || index >= ItemCount) return -1; return index; } bool GUIListBox::AreArrowsShown() const { return (ListBoxFlags & kListBox_ShowArrows) != 0; } bool GUIListBox::IsBorderShown() const { return (ListBoxFlags & kListBox_ShowBorder) != 0; } bool GUIListBox::IsSvgIndex() const { return (ListBoxFlags & kListBox_SvgIndex) != 0; } bool GUIListBox::IsInRightMargin(int x) const { if (x >= (_width - get_fixed_pixel_size(6)) && IsBorderShown() && AreArrowsShown()) return 1; return 0; } Rect GUIListBox::CalcGraphicRect(bool clipped) { if (clipped) return RectWH(0, 0, _width, _height); // TODO: need to find a way to text position, or there'll be some repetition // have to precache text and size on some events: // - translation change // - macro value change (score, overhotspot etc) Rect rc = RectWH(0, 0, _width, _height); UpdateMetrics(); const int width = _width - 1; const int pixel_size = get_fixed_pixel_size(1); int right_hand_edge = width - pixel_size - 1; // calculate the scroll bar's width if necessary if (ItemCount > VisibleItemCount &&IsBorderShown() && AreArrowsShown()) right_hand_edge -= get_fixed_pixel_size(7); Line max_line; for (int item = 0; (item < VisibleItemCount) && (item + TopItem < ItemCount); ++item) { int at_y = pixel_size + item * RowHeight; int item_index = item + TopItem; PrepareTextToDraw(Items[item_index]); Line lpos = GUI::CalcTextPositionHor(_textToDraw.GetCStr(), Font, 1 + pixel_size, right_hand_edge, at_y + 1, (FrameAlignment)TextAlignment); max_line.X2 = MAX(max_line.X2, lpos.X2); } int last_line_y = pixel_size + 1 + (VisibleItemCount - 1) * RowHeight; // Include font fixes for the first and last text line, // in case graphical height is different, and there's a VerticalOffset Line vextent = GUI::CalcFontGraphicalVExtent(Font); Rect text_rc = RectWH(0, vextent.Y1, max_line.X2 - max_line.X1 + 1, last_line_y + (vextent.Y2 - vextent.Y1)); return SumRects(rc, text_rc); } int GUIListBox::AddItem(const String &text) { Items.push_back(text); SavedGameIndex.push_back(-1); ItemCount++; MarkChanged(); return ItemCount - 1; } void GUIListBox::Clear() { if (Items.empty()) return; Items.clear(); SavedGameIndex.clear(); ItemCount = 0; SelectedItem = 0; TopItem = 0; MarkChanged(); } void GUIListBox::Draw(Bitmap *ds, int x, int y) { const int width = _width - 1; const int height = _height - 1; const int pixel_size = get_fixed_pixel_size(1); color_t text_color = ds->GetCompatibleColor(TextColor); color_t draw_color = ds->GetCompatibleColor(TextColor); if (IsBorderShown()) { ds->DrawRect(Rect(x, y, x + width, y + height), draw_color); if (pixel_size > 1) ds->DrawRect(Rect(x + 1, y + 1, x + width - 1, y + height - 1), draw_color); } int right_hand_edge = (x + width) - pixel_size - 1; // update the RowHeight and VisibleItemCount // FIXME: find a way to update this whenever relevant things change in the engine UpdateMetrics(); // draw the scroll bar in if necessary bool scrollbar = (ItemCount > VisibleItemCount) && IsBorderShown() && AreArrowsShown(); if (scrollbar) { int xstrt, ystrt; ds->DrawRect(Rect(x + width - get_fixed_pixel_size(7), y, (x + (pixel_size - 1) + width) - get_fixed_pixel_size(7), y + height), draw_color); ds->DrawRect(Rect(x + width - get_fixed_pixel_size(7), y + height / 2, x + width, y + height / 2 + (pixel_size - 1)), draw_color); xstrt = (x + width - get_fixed_pixel_size(6)) + (pixel_size - 1); ystrt = (y + height - 3) - get_fixed_pixel_size(5); draw_color = ds->GetCompatibleColor(TextColor); ds->DrawTriangle(Triangle(xstrt, ystrt, xstrt + get_fixed_pixel_size(4), ystrt, xstrt + get_fixed_pixel_size(2), ystrt + get_fixed_pixel_size(5)), draw_color); ystrt = y + 3; ds->DrawTriangle(Triangle(xstrt, ystrt + get_fixed_pixel_size(5), xstrt + get_fixed_pixel_size(4), ystrt + get_fixed_pixel_size(5), xstrt + get_fixed_pixel_size(2), ystrt), draw_color); right_hand_edge -= get_fixed_pixel_size(7); } Rect old_clip = ds->GetClip(); if (scrollbar && GUI::Options.ClipControls) ds->SetClip(Rect(x, y, right_hand_edge + 1, y + _height - 1)); for (int item = 0; (item < VisibleItemCount) && (item + TopItem < ItemCount); ++item) { int at_y = y + pixel_size + item * RowHeight; if (item + TopItem == SelectedItem) { text_color = ds->GetCompatibleColor(SelectedTextColor); if (SelectedBgColor > 0) { int stretch_to = (x + width) - pixel_size; // draw the SelectedItem item bar (if colour not transparent) draw_color = ds->GetCompatibleColor(SelectedBgColor); if ((VisibleItemCount < ItemCount) && IsBorderShown() && AreArrowsShown()) stretch_to -= get_fixed_pixel_size(7); ds->FillRect(Rect(x + pixel_size, at_y, stretch_to, at_y + RowHeight - pixel_size), draw_color); } } else text_color = ds->GetCompatibleColor(TextColor); int item_index = item + TopItem; PrepareTextToDraw(Items[item_index]); GUI::DrawTextAlignedHor(ds, _textToDraw.GetCStr(), Font, text_color, x + 1 + pixel_size, right_hand_edge, at_y + 1, (FrameAlignment)TextAlignment); } ds->SetClip(old_clip); } int GUIListBox::InsertItem(int index, const String &text) { if (index < 0 || index > ItemCount) return -1; Items.insert(Items.begin() + index, text); SavedGameIndex.insert(SavedGameIndex.begin() + index, -1); if (SelectedItem >= index) SelectedItem++; ItemCount++; MarkChanged(); return ItemCount - 1; } void GUIListBox::RemoveItem(int index) { if (index < 0 || index >= ItemCount) return; Items.erase(Items.begin() + index); SavedGameIndex.erase(SavedGameIndex.begin() + index); ItemCount--; if (SelectedItem > index) SelectedItem--; if (SelectedItem >= ItemCount) SelectedItem = -1; MarkChanged(); } void GUIListBox::SetShowArrows(bool on) { if (on != ((ListBoxFlags & kListBox_ShowArrows) != 0)) MarkChanged(); if (on) ListBoxFlags |= kListBox_ShowArrows; else ListBoxFlags &= ~kListBox_ShowArrows; } void GUIListBox::SetShowBorder(bool on) { if (on != ((ListBoxFlags & kListBox_ShowBorder) != 0)) MarkChanged(); if (on) ListBoxFlags |= kListBox_ShowBorder; else ListBoxFlags &= ~kListBox_ShowBorder; } void GUIListBox::SetSvgIndex(bool on) { if (on) ListBoxFlags |= kListBox_SvgIndex; else ListBoxFlags &= ~kListBox_SvgIndex; } void GUIListBox::SetFont(int font) { if (Font == font) return; Font = font; UpdateMetrics(); MarkChanged(); } void GUIListBox::SetItemText(int index, const String &text) { if ((index >= 0) && (index < ItemCount) && (text != Items[index])) { Items[index] = text; MarkChanged(); } } bool GUIListBox::OnMouseDown() { if (IsInRightMargin(MousePos.X)) { int top_item = TopItem; if (MousePos.Y < _height / 2 && TopItem > 0) top_item = TopItem - 1; if (MousePos.Y >= _height / 2 && ItemCount > TopItem + VisibleItemCount) top_item = TopItem + 1; if (TopItem != top_item) { TopItem = top_item; MarkChanged(); } return false; } int sel = GetItemAt(MousePos.X, MousePos.Y); if (sel < 0) return false; if (sel != SelectedItem) { SelectedItem = sel; MarkChanged(); } IsActivated = true; return false; } void GUIListBox::OnMouseMove(int x_, int y_) { MousePos.X = x_ - X; MousePos.Y = y_ - Y; } void GUIListBox::OnResized() { UpdateMetrics(); MarkChanged(); } void GUIListBox::UpdateMetrics() { int font_height = (_G(loaded_game_file_version) < kGameVersion_360_21) ? get_font_height(Font) : get_font_height_outlined(Font); RowHeight = font_height + get_fixed_pixel_size(2); // +1 top/bottom margin VisibleItemCount = _height / RowHeight; if (ItemCount <= VisibleItemCount) TopItem = 0; // reset scroll if all items are visible } // TODO: replace string serialization with StrUtil::ReadString and WriteString // methods in the future, to keep this organized. void GUIListBox::WriteToFile(Stream *out) const { GUIObject::WriteToFile(out); out->WriteInt32(ItemCount); out->WriteInt32(Font); out->WriteInt32(TextColor); out->WriteInt32(SelectedTextColor); out->WriteInt32(ListBoxFlags); out->WriteInt32(TextAlignment); out->WriteInt32(SelectedBgColor); for (int i = 0; i < ItemCount; ++i) Items[i].Write(out); } void GUIListBox::ReadFromFile(Stream *in, GuiVersion gui_version) { Clear(); GUIObject::ReadFromFile(in, gui_version); ItemCount = in->ReadInt32(); if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support SelectedItem = in->ReadInt32(); TopItem = in->ReadInt32(); MousePos.X = in->ReadInt32(); MousePos.Y = in->ReadInt32(); RowHeight = in->ReadInt32(); VisibleItemCount = in->ReadInt32(); } Font = in->ReadInt32(); TextColor = in->ReadInt32(); SelectedTextColor = in->ReadInt32(); ListBoxFlags = in->ReadInt32(); // reverse particular flags from older format if (gui_version < kGuiVersion_350) ListBoxFlags ^= kListBox_OldFmtXorMask; if (gui_version >= kGuiVersion_272b) { if (gui_version < kGuiVersion_350) { TextAlignment = ConvertLegacyGUIAlignment((LegacyGUIAlignment)in->ReadInt32()); in->ReadInt32(); // reserved1 } else { TextAlignment = (HorAlignment)in->ReadInt32(); } } else { TextAlignment = kHAlignLeft; } if (gui_version >= kGuiVersion_unkn_107) { SelectedBgColor = in->ReadInt32(); } else { SelectedBgColor = TextColor; if (SelectedBgColor == 0) SelectedBgColor = 16; } // NOTE: we leave items in game data format as a potential support for defining // ListBox contents at design-time, although Editor does not support it as of 3.5.0. Items.resize(ItemCount); SavedGameIndex.resize(ItemCount, -1); for (int i = 0; i < ItemCount; ++i) { Items[i].Read(in); } if (gui_version >= kGuiVersion_272d && gui_version < kGuiVersion_350 && (ListBoxFlags & kListBox_SvgIndex)) { // NOTE: reading into actual variables only for old savegame support for (int i = 0; i < ItemCount; ++i) SavedGameIndex[i] = in->ReadInt16(); } if (TextColor == 0) TextColor = 16; UpdateMetrics(); } void GUIListBox::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) { GUIObject::ReadFromSavegame(in, svg_ver); // Properties ListBoxFlags = in->ReadInt32(); Font = in->ReadInt32(); if (svg_ver < kGuiSvgVersion_350) { // reverse particular flags from older format ListBoxFlags ^= kListBox_OldFmtXorMask; } else { SelectedBgColor = in->ReadInt32(); SelectedTextColor = in->ReadInt32(); TextAlignment = (HorAlignment)in->ReadInt32(); TextColor = in->ReadInt32(); } // Items ItemCount = in->ReadInt32(); Items.resize(ItemCount); SavedGameIndex.resize(ItemCount); for (int i = 0; i < ItemCount; ++i) Items[i] = StrUtil::ReadString(in); // TODO: investigate this, it might be unreasonable to save and read // savegame index like that because list of savegames may easily change // in between writing and restoring the game. Perhaps clearing and forcing // this list to update on load somehow may make more sense. if (ListBoxFlags & kListBox_SvgIndex) for (int i = 0; i < ItemCount; ++i) SavedGameIndex[i] = in->ReadInt16(); TopItem = in->ReadInt32(); SelectedItem = in->ReadInt32(); UpdateMetrics(); } void GUIListBox::WriteToSavegame(Stream *out) const { GUIObject::WriteToSavegame(out); // Properties out->WriteInt32(ListBoxFlags); out->WriteInt32(Font); out->WriteInt32(SelectedBgColor); out->WriteInt32(SelectedTextColor); out->WriteInt32(TextAlignment); out->WriteInt32(TextColor); // Items out->WriteInt32(ItemCount); for (int i = 0; i < ItemCount; ++i) StrUtil::WriteString(Items[i], out); if (ListBoxFlags & kListBox_SvgIndex) for (int i = 0; i < ItemCount; ++i) out->WriteInt16(SavedGameIndex[i]); out->WriteInt32(TopItem); out->WriteInt32(SelectedItem); } } // namespace Shared } // namespace AGS } // namespace AGS3