Skip to main content

GUI Container

The GUI Container is the root container component that orchestrates the entire MistWarp application interface.

Overview

The GUI Container:

  • Manages the overall application state
  • Coordinates between major UI sections
  • Handles global application events
  • Provides the main layout structure
  • Integrates with the VM and addon system

Architecture

GUIContainer
├── MenuBarContainer
├── BlocksContainer (Workspace area)
├── StageWrapperContainer (Stage area)
├── TargetPaneContainer (Sprite/backdrop selector)
├── CostumeTabContainer (Asset management)
├── SoundTabContainer (Audio management)
└── ModalContainer (Overlays)

Component Structure

const GUIContainer = () => {
const dispatch = useDispatch();
const vm = useSelector(state => state.vm.instance);
const projectTitle = useSelector(state => state.project.title);
const isLoading = useSelector(state => state.gui.isLoading);
const activeTab = useSelector(state => state.gui.activeTab);
const isFullScreen = useSelector(state => state.gui.mode.isFullScreen);

// Initialize application
useEffect(() => {
dispatch(initializeApplication());
}, [dispatch]);

// Handle VM events
useEffect(() => {
if (vm) {
vm.on('PROJECT_LOADED', handleProjectLoaded);
vm.on('PROJECT_CHANGED', handleProjectChanged);
vm.on('RUNTIME_ERROR', handleRuntimeError);

return () => {
vm.off('PROJECT_LOADED', handleProjectLoaded);
vm.off('PROJECT_CHANGED', handleProjectChanged);
vm.off('RUNTIME_ERROR', handleRuntimeError);
};
}
}, [vm]);

if (isLoading) {
return <LoadingScreen />;
}

return (
<div className={`gui ${isFullScreen ? 'full-screen' : ''}`}>
<MenuBarContainer />
<div className="gui-body">
<div className="workspace-area">
<BlocksContainer />
</div>
<div className="stage-area">
<StageWrapperContainer />
<TargetPaneContainer />
</div>
<div className="asset-area">
{activeTab === 'costumes' && <CostumeTabContainer />}
{activeTab === 'sounds' && <SoundTabContainer />}
{activeTab === 'code' && <BlocksContainer />}
</div>
</div>
<ModalContainer />
</div>
);
};

State Management

The GUI Container connects to multiple Redux slices:

const mapStateToProps = state => ({
// VM state
vm: state.vm.instance,
vmState: state.vm.state,

// Project state
projectTitle: state.project.title,
projectId: state.project.id,
hasUnsavedChanges: state.project.hasUnsavedChanges,

// GUI state
activeTab: state.gui.activeTab,
isFullScreen: state.gui.mode.isFullScreen,
isPlayerMode: state.gui.mode.isPlayer,
isLoading: state.gui.isLoading,

// Target state
editingTarget: state.targets.editingTarget,

// Addons state
enabledAddons: state.addons.enabled,

// User state
user: state.session.user
});

const mapDispatchToProps = dispatch => ({
onInitializeApplication: () => dispatch(initializeApplication()),
onLoadProject: file => dispatch(loadProject(file)),
onSaveProject: () => dispatch(saveProject()),
onSetActiveTab: tab => dispatch(setActiveTab(tab)),
onToggleFullScreen: () => dispatch(toggleFullScreen())
});

Application Initialization

The container handles complex initialization sequences:

const initializeApplication = () => async (dispatch, getState) => {
dispatch(setLoading(true));

try {
// Initialize VM
const vm = new VirtualMachine();
dispatch(setVM(vm));

// Load user preferences
const preferences = await loadUserPreferences();
dispatch(setPreferences(preferences));

// Initialize addon system
const addons = await loadEnabledAddons();
dispatch(initializeAddons(addons));

// Load default project or restore session
const savedProject = getSavedProject();
if (savedProject) {
await dispatch(loadProject(savedProject));
} else {
await dispatch(createDefaultProject());
}

dispatch(setLoading(false));
} catch (error) {
dispatch(setError(error.message));
dispatch(setLoading(false));
}
};

Event Handling

Global Keyboard Shortcuts

const handleKeyDown = useCallback((event) => {
const { key, ctrlKey, metaKey, shiftKey } = event;
const cmd = ctrlKey || metaKey;

// Prevent browser shortcuts when focused on MistWarp
if (document.activeElement?.closest('.gui')) {
switch (true) {
case cmd && key === 'n':
event.preventDefault();
dispatch(createNewProject());
break;

case cmd && key === 's':
event.preventDefault();
dispatch(saveProject());
break;

case cmd && key === 'z' && !shiftKey:
event.preventDefault();
dispatch(undo());
break;

case cmd && (key === 'y' || (key === 'z' && shiftKey)):
event.preventDefault();
dispatch(redo());
break;

case key === 'F11':
event.preventDefault();
dispatch(toggleFullScreen());
break;
}
}
}, [dispatch]);

useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [handleKeyDown]);

Window Events

useEffect(() => {
const handleBeforeUnload = (event) => {
const state = store.getState();
if (state.project.hasUnsavedChanges) {
event.preventDefault();
event.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
}
};

const handleResize = () => {
dispatch(setWindowSize({
width: window.innerWidth,
height: window.innerHeight
}));
};

window.addEventListener('beforeunload', handleBeforeUnload);
window.addEventListener('resize', handleResize);

return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
window.removeEventListener('resize', handleResize);
};
}, []);

Layout Management

Responsive Design

const useResponsiveLayout = () => {
const [layoutMode, setLayoutMode] = useState('desktop');

useEffect(() => {
const updateLayout = () => {
const width = window.innerWidth;
if (width < 768) {
setLayoutMode('mobile');
} else if (width < 1024) {
setLayoutMode('tablet');
} else {
setLayoutMode('desktop');
}
};

updateLayout();
window.addEventListener('resize', updateLayout);
return () => window.removeEventListener('resize', updateLayout);
}, []);

return layoutMode;
};

Adaptive UI

const AdaptiveGUI = () => {
const layoutMode = useResponsiveLayout();

const renderMobileLayout = () => (
<div className="gui-mobile">
<MobileMenuBar />
<TabbedInterface>
<Tab label="Code"><BlocksContainer /></Tab>
<Tab label="Costumes"><CostumeTabContainer /></Tab>
<Tab label="Sounds"><SoundTabContainer /></Tab>
</TabbedInterface>
<MobileStage />
</div>
);

const renderDesktopLayout = () => (
<div className="gui-desktop">
<MenuBarContainer />
<div className="gui-body">
<BlocksContainer />
<StageWrapperContainer />
<AssetTabs />
</div>
</div>
);

return layoutMode === 'mobile' ? renderMobileLayout() : renderDesktopLayout();
};

Error Handling

Error Boundaries

class GUIErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error) {
return { hasError: true, error };
}

componentDidCatch(error, errorInfo) {
console.error('GUI Error:', error, errorInfo);

// Report error to analytics
if (window.analytics) {
window.analytics.track('GUI Error', {
error: error.message,
stack: error.stack,
errorInfo
});
}
}

render() {
if (this.state.hasError) {
return (
<ErrorScreen
error={this.state.error}
onReload={() => window.location.reload()}
onReport={() => this.reportError()}
/>
);
}

return this.props.children;
}
}

Runtime Error Handling

const handleRuntimeError = useCallback((error) => {
console.error('Runtime error:', error);

dispatch(addNotification({
type: 'error',
message: `Runtime error: ${error.message}`,
timeout: 5000
}));

// Stop project if error is critical
if (error.critical) {
dispatch(stopProject());
}
}, [dispatch]);

Performance Optimization

Code Splitting

const LazyBlocksContainer = React.lazy(() => import('./BlocksContainer'));
const LazyCostumeTab = React.lazy(() => import('./CostumeTabContainer'));

const GUIContainer = () => (
<div className="gui">
<MenuBarContainer />
<Suspense fallback={<LoadingSpinner />}>
<Switch>
<Route path="/blocks" component={LazyBlocksContainer} />
<Route path="/costumes" component={LazyCostumeTab} />
</Switch>
</Suspense>
</div>
);

Memoization

const GUIContainer = React.memo(() => {
// Component implementation
}, (prevProps, nextProps) => {
// Custom comparison logic
return prevProps.projectId === nextProps.projectId &&
prevProps.activeTab === nextProps.activeTab;
});

Testing

describe('GUIContainer', () => {
let store;

beforeEach(() => {
store = createMockStore({
vm: { instance: null },
gui: { activeTab: 'code', isLoading: false },
project: { title: 'Test Project' }
});
});

it('should render main GUI structure', () => {
const wrapper = mount(
<Provider store={store}>
<GUIContainer />
</Provider>
);

expect(wrapper.find('.gui')).toHaveLength(1);
expect(wrapper.find('MenuBarContainer')).toHaveLength(1);
expect(wrapper.find('BlocksContainer')).toHaveLength(1);
});

it('should handle VM initialization', async () => {
const wrapper = mount(
<Provider store={store}>
<GUIContainer />
</Provider>
);

await act(async () => {
store.dispatch(setVM(new MockVM()));
});

wrapper.update();

const actions = store.getActions();
expect(actions).toContainEqual({
type: 'vm/setInstance',
payload: expect.any(MockVM)
});
});
});