diff --git a/neo/tools/radiant/CamWnd.cpp b/neo/tools/radiant/CamWnd.cpp index bc30d83d..63ffba14 100644 --- a/neo/tools/radiant/CamWnd.cpp +++ b/neo/tools/radiant/CamWnd.cpp @@ -33,6 +33,7 @@ If you have questions concerning this license or the applicable additional terms #include "Radiant.h" #include "XYWnd.h" #include "CamWnd.h" +#include "OutlinerDlg.h" #include "splines.h" #include @@ -826,6 +827,163 @@ static void CameraNav_DrawOverlay(CCamWnd* cam) { } + +/* +======================== +Outliner-aware entity target/path lines + +The original DrawPathLines() does not know about the outliner visibility list. +When no entity is hidden, keep using the original function so the normal editor +style is unchanged. When something is hidden, draw a filtered equivalent that +skips links where either endpoint is hidden. +======================== +*/ +static bool CamWnd_IsTargetLineKey(const char* key) { + if (key == NULL || key[0] == '\0') { + return false; + } + + if (idStr::Icmp(key, "targetname") == 0) { + return false; + } + + if (idStr::Icmp(key, "next") == 0) { + return true; + } + + return idStr::Icmpn(key, "target", 6) == 0; +} + +static bool CamWnd_EntityNameMatches(entity_t* ent, const char* name) { + if (ent == NULL || name == NULL || name[0] == '\0') { + return false; + } + + const char* entName = ValueForKey(ent, "name"); + if (entName != NULL && entName[0] != '\0' && idStr::Icmp(entName, name) == 0) { + return true; + } + + const char* targetName = ValueForKey(ent, "targetname"); + if (targetName != NULL && targetName[0] != '\0' && idStr::Icmp(targetName, name) == 0) { + return true; + } + + return false; +} + +static bool CamWnd_GetEntityLinePoint(entity_t* ent, idVec3& point) { + if (ent == NULL) { + return false; + } + + if (GetVectorForKey(ent, "origin", point)) { + return true; + } + + idBounds bounds; + bounds.Clear(); + bool haveBounds = false; + + for (brush_t* brush = ent->brushes.onext; brush != NULL && brush != &ent->brushes; brush = brush->onext) { + bounds.AddPoint(brush->mins); + bounds.AddPoint(brush->maxs); + haveBounds = true; + } + + if (haveBounds) { + point = bounds.GetCenter(); + return true; + } + + return false; +} + +static void CamWnd_DrawVisibleTargetLine(entity_t* source, entity_t* target) { + if (source == NULL || target == NULL || source == target) { + return; + } + + if (!Outliner_IsEntityVisible(source) || !Outliner_IsEntityVisible(target)) { + return; + } + + idVec3 start; + idVec3 end; + if (!CamWnd_GetEntityLinePoint(source, start) || !CamWnd_GetEntityLinePoint(target, end)) { + return; + } + + glVertex3fv(start.ToFloatPtr()); + glVertex3fv(end.ToFloatPtr()); +} + +static void CamWnd_DrawVisibleTargetLinesForName(entity_t* source, const char* targetName) { + if (targetName == NULL || targetName[0] == '\0') { + return; + } + + if (world_entity != NULL && CamWnd_EntityNameMatches(world_entity, targetName)) { + CamWnd_DrawVisibleTargetLine(source, world_entity); + } + + if (entities.next == NULL) { + return; + } + + for (entity_t* target = entities.next; target != &entities; target = target->next) { + if (CamWnd_EntityNameMatches(target, targetName)) { + CamWnd_DrawVisibleTargetLine(source, target); + } + } +} + +static void CamWnd_DrawVisibleTargetLinesForEntity(entity_t* source) { + if (source == NULL || !Outliner_IsEntityVisible(source)) { + return; + } + + const int numKeys = source->epairs.GetNumKeyVals(); + for (int keyIndex = 0; keyIndex < numKeys; keyIndex++) { + const idKeyValue* kv = source->epairs.GetKeyVal(keyIndex); + if (kv == NULL) { + continue; + } + + if (!CamWnd_IsTargetLineKey(kv->GetKey().c_str())) { + continue; + } + + CamWnd_DrawVisibleTargetLinesForName(source, kv->GetValue().c_str()); + } +} + +static void CamWnd_DrawVisiblePathLines() { + if (!Outliner_HasHiddenEntities()) { + DrawPathLines(); + return; + } + + glPushAttrib(GL_CURRENT_BIT); + globalImages->BindNull(); + glDisable(GL_BLEND); + glLineWidth(1.0f); + glColor3f(1.0f, 0.75f, 0.0f); + + glBegin(GL_LINES); + if (world_entity != NULL) { + CamWnd_DrawVisibleTargetLinesForEntity(world_entity); + } + if (entities.next != NULL) { + for (entity_t* source = entities.next; source != &entities; source = source->next) { + CamWnd_DrawVisibleTargetLinesForEntity(source); + } + } + glEnd(); + + glPopAttrib(); +} + /* ======================== Camera window embedded menu bar @@ -1213,7 +1371,15 @@ static void CamWnd_UpdateRuntimeState(CCamWnd* cam, bool renderMode, bool rebuil } static bool CamWnd_MenuFilterBrush(CCamWnd* cam, brush_t* brush) { - if (!cam || !brush) { + if (!brush) { + return false; + } + + if (brush->owner != NULL && !Outliner_IsEntityVisible(brush->owner)) { + return true; + } + + if (!cam) { return false; } @@ -2475,7 +2641,7 @@ void CCamWnd::Cam_Draw() { // draw pointfile glEnable(GL_DEPTH_TEST); - DrawPathLines(); + CamWnd_DrawVisiblePathLines(); if (g_qeglobals.d_pointfile_display_list) { Pointfile_Draw(); diff --git a/neo/tools/radiant/OutlinerDlg.cpp b/neo/tools/radiant/OutlinerDlg.cpp index 1aad0606..9245efd1 100644 --- a/neo/tools/radiant/OutlinerDlg.cpp +++ b/neo/tools/radiant/OutlinerDlg.cpp @@ -42,6 +42,144 @@ static const int OUTLINER_FILTER_LABEL_W = 42; static const int OUTLINER_FILTER_H = 22; static const int OUTLINER_GAP = 6; + +static CPtrArray s_outlinerHiddenEntities; +static CString s_outlinerVisibilityMapName; +static entity_t* s_outlinerVisibilityWorldEntity = NULL; +static entity_t* s_outlinerVisibilityFirstEntity = NULL; +static int s_outlinerVisibilityEntityCount = -1; +static int s_outlinerVisibilityMapModified = -999999; +static bool s_outlinerVisibilityInitialized = false; + +static const char* Outliner_CurrentMapName() { + return currentmap ? currentmap : ""; +} + +static entity_t* Outliner_FirstMapEntity() { + if (entities.next != NULL && entities.next != &entities) { + return entities.next; + } + return NULL; +} + +static bool Outliner_ArrayContainsPtr(const CPtrArray& array, entity_t* ent) { + for (int i = 0; i < array.GetSize(); i++) { + if ((entity_t*)array.GetAt(i) == ent) { + return true; + } + } + return false; +} + +static void Outliner_ClearHiddenEntities() { + s_outlinerHiddenEntities.RemoveAll(); +} + +static void Outliner_SyncVisibilityMapIdentity() { + s_outlinerVisibilityInitialized = true; + s_outlinerVisibilityWorldEntity = world_entity; + s_outlinerVisibilityFirstEntity = Outliner_FirstMapEntity(); + s_outlinerVisibilityEntityCount = g_qeglobals.d_num_entities; + s_outlinerVisibilityMapModified = mapModified; + s_outlinerVisibilityMapName = Outliner_CurrentMapName(); +} + +void Outliner_ResetEntityVisibility() { + Outliner_ClearHiddenEntities(); + Outliner_SyncVisibilityMapIdentity(); +} + +static void Outliner_EnsureVisibilityMap() { + const char* mapName = Outliner_CurrentMapName(); + const entity_t* firstEntity = Outliner_FirstMapEntity(); + bool resetVisibility = false; + + if (!s_outlinerVisibilityInitialized) { + resetVisibility = true; + } + else if (s_outlinerVisibilityWorldEntity != world_entity) { + resetVisibility = true; + } + else if (s_outlinerVisibilityMapName.CompareNoCase(mapName) != 0) { + resetVisibility = true; + } + else if (mapModified < s_outlinerVisibilityMapModified) { + // Loading or creating a map normally resets mapModified. Treat that as + // a new editor visibility session so every entity starts checked. + resetVisibility = true; + } + else if (mapModified <= 0 && s_outlinerVisibilityFirstEntity != firstEntity) { + // Some map/new-map paths reuse the same worldspawn pointer and leave + // currentmap empty. If the list head changed while the map is clean, + // this is almost certainly a freshly loaded/new map, not an edit. + resetVisibility = true; + } + + if (resetVisibility) { + Outliner_ClearHiddenEntities(); + } + + Outliner_SyncVisibilityMapIdentity(); +} + +static int Outliner_FindHiddenEntity(entity_t* ent) { + for (int i = 0; i < s_outlinerHiddenEntities.GetSize(); i++) { + if ((entity_t*)s_outlinerHiddenEntities.GetAt(i) == ent) { + return i; + } + } + return -1; +} + +static void Outliner_PruneHiddenEntities(const CPtrArray& currentEntities) { + Outliner_EnsureVisibilityMap(); + + for (int i = s_outlinerHiddenEntities.GetSize() - 1; i >= 0; i--) { + entity_t* ent = (entity_t*)s_outlinerHiddenEntities.GetAt(i); + if (!Outliner_ArrayContainsPtr(currentEntities, ent)) { + s_outlinerHiddenEntities.RemoveAt(i); + } + } +} + +bool Outliner_IsEntityVisible(entity_t* ent) { + if (ent == NULL) { + return true; + } + + Outliner_EnsureVisibilityMap(); + return Outliner_FindHiddenEntity(ent) < 0; +} + +bool Outliner_HasHiddenEntities() { + Outliner_EnsureVisibilityMap(); + return s_outlinerHiddenEntities.GetSize() > 0; +} + +bool Outliner_IsEntityLinkVisible(entity_t* source, entity_t* target) { + return Outliner_IsEntityVisible(source) && Outliner_IsEntityVisible(target); +} + +void Outliner_SetEntityVisible(entity_t* ent, bool visible) { + if (ent == NULL) { + return; + } + + Outliner_EnsureVisibilityMap(); + + const int index = Outliner_FindHiddenEntity(ent); + if (visible) { + if (index >= 0) { + s_outlinerHiddenEntities.RemoveAt(index); + } + } + else { + if (index < 0) { + s_outlinerHiddenEntities.Add(ent); + } + } +} + BEGIN_MESSAGE_MAP(COutlinerDlg, CWnd) ON_WM_CREATE() ON_WM_SIZE() @@ -53,6 +191,8 @@ BEGIN_MESSAGE_MAP(COutlinerDlg, CWnd) ON_EN_CHANGE(COutlinerDlg::IDC_OUTLINER_FILTER, OnFilterChanged) ON_NOTIFY(NM_DBLCLK, COutlinerDlg::IDC_OUTLINER_TREE, OnTreeDblClick) ON_NOTIFY(NM_RCLICK, COutlinerDlg::IDC_OUTLINER_TREE, OnTreeRightClick) + ON_NOTIFY(NM_CLICK, COutlinerDlg::IDC_OUTLINER_TREE, OnTreeClick) + ON_MESSAGE(COutlinerDlg::WM_OUTLINER_APPLY_CHECK, OnApplyTreeCheck) END_MESSAGE_MAP() COutlinerDlg::COutlinerDlg() { @@ -133,7 +273,7 @@ int COutlinerDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) { m_tree.Create( WS_CHILD | WS_VISIBLE | WS_TABSTOP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | - TVS_SHOWSELALWAYS | TVS_DISABLEDRAGDROP, + TVS_SHOWSELALWAYS | TVS_DISABLEDRAGDROP | TVS_CHECKBOXES, CRect(0, 0, 10, 10), this, IDC_OUTLINER_TREE @@ -299,6 +439,15 @@ BOOL COutlinerDlg::PreTranslateMessage(MSG* pMsg) { OpenEntityProperties(SelectedEntity()); return TRUE; } + + if (pMsg->wParam == VK_SPACE && pMsg->hwnd == m_tree.GetSafeHwnd()) { + entity_t* ent = SelectedEntity(); + if (ent != NULL) { + ToggleEntityVisibility(ent, false); + return TRUE; + } + } + if (pMsg->wParam == VK_F5) { Refresh(true); return TRUE; @@ -349,7 +498,30 @@ void COutlinerDlg::Refresh(bool force) { entity_t* oldSelection = SelectedEntity(); + const char* mapName = Outliner_CurrentMapName(); + const bool hadPreviousRefresh = (m_lastMapModified != -999999); + const bool mapIdentityChanged = + (!s_outlinerVisibilityInitialized) || + (m_lastWorldEntity != world_entity) || + (m_lastMapName.CompareNoCase(mapName) != 0) || + (hadPreviousRefresh && mapModified < m_lastMapModified) || + (hadPreviousRefresh && mapModified <= 0 && s_outlinerVisibilityFirstEntity != Outliner_FirstMapEntity()); + + // File->New in this editor can leave the same empty map name around. + // When that happens, make the new map start from a clean all-visible state + // instead of inheriting hidden pointers from the previous contents. + const bool likelyNewBlankMap = + (s_outlinerHiddenEntities.GetSize() > 0) && + (m_lastEntityCount > 1) && + (g_qeglobals.d_num_entities <= 1) && + (mapName[0] == '\0'); + + if (mapIdentityChanged || likelyNewBlankMap) { + Outliner_ResetEntityVisibility(); + } + BuildGraph(); + Outliner_PruneHiddenEntities(m_entities); m_tree.SetRedraw(FALSE); m_tree.DeleteAllItems(); @@ -383,6 +555,7 @@ void COutlinerDlg::Refresh(bool force) { if (loopGroup == NULL) { loopGroup = m_tree.InsertItem("Target loops / no root", TVI_ROOT, TVI_LAST); + m_tree.SetItemState(loopGroup, 0, TVIS_STATEIMAGEMASK); insertedAny = true; } @@ -395,10 +568,14 @@ void COutlinerDlg::Refresh(bool force) { } if (!insertedAny) { + HTREEITEM emptyItem = NULL; if (m_filterText.IsEmpty()) { - m_tree.InsertItem("", TVI_ROOT, TVI_LAST); + emptyItem = m_tree.InsertItem("", TVI_ROOT, TVI_LAST); } else { - m_tree.InsertItem("", TVI_ROOT, TVI_LAST); + emptyItem = m_tree.InsertItem("", TVI_ROOT, TVI_LAST); + } + if (emptyItem != NULL) { + m_tree.SetItemState(emptyItem, 0, TVIS_STATEIMAGEMASK); } } @@ -425,7 +602,7 @@ void COutlinerDlg::Refresh(bool force) { m_lastWorldEntity = world_entity; m_lastEntityCount = g_qeglobals.d_num_entities; m_lastMapModified = mapModified; - m_lastMapName = currentmap; + m_lastMapName = Outliner_CurrentMapName(); } void COutlinerDlg::BuildGraph() { @@ -565,6 +742,10 @@ bool COutlinerDlg::IsTargetKey(const char* key) const { return false; } + if (idStr::Icmp(key, "next") == 0) { + return true; + } + return idStr::Icmpn(key, "target", 6) == 0; } @@ -723,6 +904,7 @@ HTREEITEM COutlinerDlg::InsertEntityRecursive(entity_t* ent, HTREEITEM parent, C HTREEITEM item = m_tree.InsertItem(label, parent, TVI_LAST); m_tree.SetItemData(item, (DWORD_PTR)AllocItemData(ent, cycleRef)); + m_tree.SetCheck(item, Outliner_IsEntityVisible(ent) ? TRUE : FALSE); const int nodeIndex = FindNode(ent); if (nodeIndex >= 0) { @@ -787,12 +969,170 @@ void COutlinerDlg::ExpandAll(HTREEITEM start) { } } +void COutlinerDlg::OnTreeClick(NMHDR* pNMHDR, LRESULT* pResult) { + CPoint point; + ::GetCursorPos(&point); + m_tree.ScreenToClient(&point); + + UINT flags = 0; + HTREEITEM item = m_tree.HitTest(point, &flags); + + if (item != NULL && (flags & TVHT_ONITEMSTATEICON) && EntityFromItem(item) != NULL) { + m_tree.SelectItem(item); + + // Let the tree control finish changing the checkbox before we read it. + PostMessage(WM_OUTLINER_APPLY_CHECK, (WPARAM)item, 0); + } + + *pResult = 0; +} + +LRESULT COutlinerDlg::OnApplyTreeCheck(WPARAM wParam, LPARAM lParam) { + if (!m_tree.GetSafeHwnd()) { + return 0; + } + + HTREEITEM item = (HTREEITEM)wParam; + if (item == NULL) { + return 0; + } + + entity_t* ent = EntityFromItem(item); + if (ent == NULL) { + return 0; + } + + const bool visible = m_tree.GetCheck(item) != FALSE; + Outliner_SetEntityVisible(ent, visible); + + ApplyVisibilityChange(); + return 0; +} + +void COutlinerDlg::CollectEntityAndChildren(entity_t* ent, CPtrArray& out, CPtrArray& stack) const { + if (ent == NULL) { + return; + } + + if (ArrayContains(out, ent)) { + return; + } + + out.Add(ent); + + if (ArrayContains(stack, ent)) { + return; + } + + stack.Add(ent); + + for (int i = 0; i < m_edges.GetSize(); i++) { + const outlinerEdge_t& edge = m_edges.GetAt(i); + if (edge.source != ent) { + continue; + } + + CollectEntityAndChildren(edge.target, out, stack); + } + + stack.RemoveAt(stack.GetSize() - 1); +} + +void COutlinerDlg::SetEntityVisibility(entity_t* ent, bool visible, bool includeChildren) { + if (ent == NULL) { + return; + } + + if (includeChildren) { + CPtrArray entitiesToChange; + CPtrArray stack; + + CollectEntityAndChildren(ent, entitiesToChange, stack); + + for (int i = 0; i < entitiesToChange.GetSize(); i++) { + Outliner_SetEntityVisible((entity_t*)entitiesToChange.GetAt(i), visible); + } + } + else { + Outliner_SetEntityVisible(ent, visible); + } + + ApplyVisibilityChange(); +} + +void COutlinerDlg::ToggleEntityVisibility(entity_t* ent, bool includeChildren) { + if (ent == NULL) { + return; + } + + const bool newVisible = !Outliner_IsEntityVisible(ent); + SetEntityVisibility(ent, newVisible, includeChildren); +} + +void COutlinerDlg::ShowOnlyEntityAndChildren(entity_t* ent) { + if (ent == NULL) { + return; + } + + CPtrArray keepVisible; + CPtrArray stack; + + CollectEntityAndChildren(ent, keepVisible, stack); + + for (int i = 0; i < m_entities.GetSize(); i++) { + entity_t* candidate = (entity_t*)m_entities.GetAt(i); + if (i == 0) + { + Outliner_SetEntityVisible(candidate, true); + continue; + } + Outliner_SetEntityVisible(candidate, ArrayContains(keepVisible, candidate)); + } + + ApplyVisibilityChange(); +} + +void COutlinerDlg::ShowAllEntities() { + Outliner_ResetEntityVisibility(); + ApplyVisibilityChange(); +} + +void COutlinerDlg::UpdateAllCheckmarks(HTREEITEM start) { + HTREEITEM item = start; + while (item != NULL) { + entity_t* ent = EntityFromItem(item); + if (ent != NULL) { + m_tree.SetCheck(item, Outliner_IsEntityVisible(ent) ? TRUE : FALSE); + } + + HTREEITEM child = m_tree.GetChildItem(item); + if (child != NULL) { + UpdateAllCheckmarks(child); + } + + item = m_tree.GetNextSiblingItem(item); + } +} + +void COutlinerDlg::ApplyVisibilityChange() { + if (m_tree.GetSafeHwnd()) { + m_tree.SetRedraw(FALSE); + UpdateAllCheckmarks(m_tree.GetRootItem()); + m_tree.SetRedraw(TRUE); + m_tree.Invalidate(); + } + + Sys_UpdateWindows(W_ALL); +} + void COutlinerDlg::OnTreeDblClick(NMHDR* pNMHDR, LRESULT* pResult) { OpenEntityProperties(SelectedEntity()); *pResult = 0; } void COutlinerDlg::OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult) { + RefreshIfNeeded(); + CPoint screenPoint; ::GetCursorPos(&screenPoint); @@ -806,12 +1146,23 @@ void COutlinerDlg::OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult) { } entity_t* ent = SelectedEntity(); + const bool entVisible = Outliner_IsEntityVisible(ent); + + CString toggleSelfText; + CString toggleChildrenText; + toggleSelfText.Format("%s this entity", entVisible ? "Hide" : "Show"); + toggleChildrenText.Format("%s this entity + children", entVisible ? "Hide" : "Show"); CMenu menu; menu.CreatePopupMenu(); menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_OPEN_PROPERTIES, "Open entity properties"); menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_MOVE_TO_ENTITY, "Move to entity"); menu.AppendMenu(MF_SEPARATOR); + menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_TOGGLE_VISIBILITY, toggleSelfText); + menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_TOGGLE_VISIBILITY_CHILDREN, toggleChildrenText); + menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_SHOW_ONLY_THIS_AND_CHILDREN, "Show only this entity + children"); + menu.AppendMenu(MF_STRING, ID_OUTLINER_SHOW_ALL, "Show all entities"); + menu.AppendMenu(MF_SEPARATOR); menu.AppendMenu(MF_STRING, ID_OUTLINER_REFRESH, "Refresh"); const int command = menu.TrackPopupMenu( @@ -828,6 +1179,18 @@ void COutlinerDlg::OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult) { case ID_OUTLINER_MOVE_TO_ENTITY: MoveToEntity(ent); break; + case ID_OUTLINER_TOGGLE_VISIBILITY: + ToggleEntityVisibility(ent, false); + break; + case ID_OUTLINER_TOGGLE_VISIBILITY_CHILDREN: + ToggleEntityVisibility(ent, true); + break; + case ID_OUTLINER_SHOW_ONLY_THIS_AND_CHILDREN: + ShowOnlyEntityAndChildren(ent); + break; + case ID_OUTLINER_SHOW_ALL: + ShowAllEntities(); + break; case ID_OUTLINER_REFRESH: Refresh(true); break; diff --git a/neo/tools/radiant/OutlinerDlg.h b/neo/tools/radiant/OutlinerDlg.h index 1781380c..87cf964d 100644 --- a/neo/tools/radiant/OutlinerDlg.h +++ b/neo/tools/radiant/OutlinerDlg.h @@ -43,7 +43,12 @@ public: ID_OUTLINER_OPEN_PROPERTIES = 0x7310, ID_OUTLINER_MOVE_TO_ENTITY = 0x7311, ID_OUTLINER_REFRESH = 0x7312, - OUTLINER_REFRESH_TIMER = 0x7320 + ID_OUTLINER_TOGGLE_VISIBILITY = 0x7313, + ID_OUTLINER_TOGGLE_VISIBILITY_CHILDREN = 0x7314, + ID_OUTLINER_SHOW_ONLY_THIS_AND_CHILDREN = 0x7315, + ID_OUTLINER_SHOW_ALL = 0x7316, + OUTLINER_REFRESH_TIMER = 0x7320, + WM_OUTLINER_APPLY_CHECK = WM_APP + 0x321 }; COutlinerDlg(); @@ -71,6 +76,8 @@ protected: afx_msg void OnFilterChanged(); void OnTreeDblClick(NMHDR* pNMHDR, LRESULT* pResult); void OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult); + void OnTreeClick(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg LRESULT OnApplyTreeCheck(WPARAM wParam, LPARAM lParam); virtual BOOL PreTranslateMessage(MSG* pMsg); @@ -128,7 +135,21 @@ private: HTREEITEM FindTreeItemForEntity(entity_t* ent, HTREEITEM start) const; void ExpandAll(HTREEITEM start); + void CollectEntityAndChildren(entity_t* ent, CPtrArray& out, CPtrArray& stack) const; + void SetEntityVisibility(entity_t* ent, bool visible, bool includeChildren); + void ToggleEntityVisibility(entity_t* ent, bool includeChildren); + void ShowOnlyEntityAndChildren(entity_t* ent); + void ShowAllEntities(); + void UpdateAllCheckmarks(HTREEITEM start); + void ApplyVisibilityChange(); + bool NeedsRefresh() const; bool GetEntityCenter(entity_t* ent, idVec3& center) const; void ApplyFont(); }; + +bool Outliner_IsEntityVisible(entity_t* ent); +void Outliner_SetEntityVisible(entity_t* ent, bool visible); +void Outliner_ResetEntityVisibility(); +bool Outliner_HasHiddenEntities(); +bool Outliner_IsEntityLinkVisible(entity_t* source, entity_t* target);