Added SDK

This commit is contained in:
Andrew Zambazos
2026-06-11 14:01:22 +12:00
commit c0395a49bd
2155 changed files with 451005 additions and 0 deletions
@@ -0,0 +1,12 @@
set(SOURCES "src/Browser.h"
"src/Browser.cpp"
"src/Tab.h"
"src/Tab.cpp"
"src/UI.h"
"src/UI.cpp"
"src/main.cpp")
set(NEEDS_INSPECTOR TRUE)
add_app(Sample8 ${SOURCES})
INSTALL(DIRECTORY "${ULTRALIGHT_INSPECTOR_DIR}" DESTINATION "${INSTALL_PATH}/assets")
File diff suppressed because one or more lines are too long
@@ -0,0 +1,213 @@
.chrome-tabs {
box-sizing: border-box;
position: relative;
font-size: 10px;
height: 40px;
/* background: linear-gradient(#dad9da, #d9d8d9); */
padding: 0.38em 1.2em 0.0em 1.2em;
border-radius: 0.5em 0.5em 0 0;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
.chrome-tabs * {
box-sizing: inherit;
font-family: inherit;
cursor: default;
}
#chrome-tabs-add-tab {
background: linear-gradient(0deg, #232330, #282836);
box-shadow: 0px 2px 0px 0px rgba(0, 0, 0, 0.4);
display: block;
position: absolute;
right: 0px;
padding: 5px 10px;
margin-top: 9px;
margin-right: 3px;
border: 0.8px solid #252532;
border-radius: 8px;
font-size: 12px;
z-index: 1000000;
height: 24px;
color: #c4c2d0 !important;
}
#chrome-tabs-add-tab:active {
background: linear-gradient(0deg, #262633, #1e1e29);
box-shadow: none;
top: 2px;
}
.chrome-tabs .chrome-tabs-bottom-bar {
position: absolute;
bottom: 0;
height: 0.40em;
left: 0;
width: 100%;
z-index: 20;
border-bottom: 0.8px solid #252532;
}
.chrome-tabs .chrome-tabs-content {
position: relative;
width: calc(100% - 75px);
height: 100%;
overflow: hidden;
}
.chrome-tabs .chrome-tab {
position: absolute;
left: 0;
height: 37px;
width: 24em;
border: 0;
margin: 0;
z-index: 1;
top: 2px;
color: #c4c2d0;
}
.chrome-tabs .chrome-tab,
.chrome-tabs .chrome-tab * {
user-select: none;
cursor: default;
}
.chrome-tabs .chrome-tab .chrome-tab-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
width: calc(100% - 8px);
height: 100%;
background: linear-gradient(180deg, #212130, #1c1c24);
border-radius: 8px 8px 0 0;
margin: 2px 4px 4px 4px;
}
.chrome-tabs .chrome-tab .chrome-tab-background>svg {
width: 100%;
height: 100%;
}
.chrome-tabs .chrome-tab .chrome-tab-background>svg .chrome-tab-shadow {
fill: none;
stroke: rgba(180, 180, 180, 1.0);
stroke-width: 1.0px;
}
.chrome-tabs .chrome-tab .chrome-tab-background>svg .chrome-tab-background {
fill: #c4c2d0;
transform: translateX(0.25px) translateY(0.25px);
}
.chrome-tabs .chrome-tab.chrome-tab-current {
z-index: 999;
color: white;
}
.chrome-tabs .chrome-tab.chrome-tab-current .chrome-tab-background {
background: linear-gradient(180deg, #313141, #282836);
/*box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.4); */
border: 0.7px solid #292934;
border-bottom: none;
}
.chrome-tabs .chrome-tab.chrome-tab-just-added {
top: 14px;
animation: chrome-tab-just-added 120ms forwards ease-in-out;
}
@keyframes chrome-tab-just-added {
to {
top: 2px;
}
}
.chrome-tabs.chrome-tabs-sorting .chrome-tab:not(.chrome-tab-currently-dragged),
.chrome-tabs:not(.chrome-tabs-sorting) .chrome-tab.chrome-tab-just-dragged {
transition: transform 120ms ease-in-out;
}
.chrome-tabs .chrome-tab-favicon,
.chrome-tabs .chrome-tab-spinner {
position: relative;
margin-left: 1.6em;
height: 14px;
width: 14px;
background-size: 14px;
margin-top: 10px;
z-index: 3;
display: inline-block;
vertical-align: top;
pointer-events: none;
}
.chrome-tabs .chrome-tab-spinner {
content: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJsb2FkZXItMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCiAgICAgd2lkdGg9IjE0cHgiIGhlaWdodD0iMTRweCIgdmlld0JveD0iMCAwIDUwIDUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MCA1MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgogIDxwYXRoIGZpbGw9IiM3YTc2OGYiIGQ9Ik00My45MzUsMjUuMTQ1YzAtMTAuMzE4LTguMzY0LTE4LjY4My0xOC42ODMtMTguNjgzYy0xMC4zMTgsMC0xOC42ODMsOC4zNjUtMTguNjgzLDE4LjY4M2g0LjA2OGMwLTguMDcxLDYuNTQzLTE0LjYxNSwxNC42MTUtMTQuNjE1YzguMDcyLDAsMTQuNjE1LDYuNTQzLDE0LjYxNSwxNC42MTVINDMuOTM1eiI+CiAgICA8YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVUeXBlPSJ4bWwiCiAgICAgIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIKICAgICAgdHlwZT0icm90YXRlIgogICAgICBmcm9tPSIwIDI1IDI1IgogICAgICB0bz0iMzYwIDI1IDI1IgogICAgICBkdXI9IjAuNnMiCiAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIi8+CiAgICA8L3BhdGg+CiAgPC9zdmc+Cg==");
}
.chrome-tabs .chrome-tab-title {
position: relative;
display: inline-block;
vertical-align: top;
padding: 0 0.5em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
margin-top: 9px;
max-width: calc(100% - 5em);
pointer-events: none;
}
.chrome-tabs .chrome-tab-close {
position: absolute;
width: 1.4em;
height: 1.4em;
border-radius: 50%;
z-index: 2;
right: 1.4em;
top: 11px;
}
.chrome-tabs .chrome-tab-close::before {
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><path stroke='%235a5a5a' stroke-width='1.3' d='M4 4 L10 10 M10 4 L4 10'></path></svg>");
position: absolute;
display: block;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.chrome-tabs .chrome-tab-close:hover::before,
.chrome-tabs .chrome-tab-close:hover:active::before {
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><path stroke='%23fff' stroke-width='1.3' d='M4 4 L10 10 M10 4 L4 10'></path></svg>");
}
.chrome-tabs .chrome-tab-close:hover {
background: #e25c4b;
}
.chrome-tabs .chrome-tab-close:hover:active {
background: #b74a3b;
}
.chrome-tabs .chrome-tab {
width: 243px
}
.chrome-tabs .chrome-tab:nth-child(1) {
transform: translate3d(0px, 0, 0)
}
.chrome-tabs .chrome-tab:nth-child(2) {
transform: translate3d(229px, 0, 0)
}
@@ -0,0 +1,272 @@
(function(){
const isNodeContext = typeof module !== 'undefined' && typeof module.exports !== 'undefined'
if (isNodeContext) {
Draggabilly = require('draggabilly')
}
const tabTemplate = `
<div class="chrome-tab">
<div class="chrome-tab-background">
</div>
<div class="chrome-tab-favicon"></div>
<div class="chrome-tab-spinner"></div>
<div class="chrome-tab-title"></div>
<div class="chrome-tab-close"></div>
</div>
`
const defaultTabProperties = {
title: '',
favicon: ''
}
let instanceId = 0
let tabId = 0
class ChromeTabs {
constructor() {
this.draggabillyInstances = []
}
init(el, options) {
this.el = el
this.options = options
this.instanceId = instanceId
this.el.setAttribute('data-chrome-tabs-instance-id', this.instanceId)
instanceId += 1
this.tabId = tabId
this.setupStyleEl()
this.setupEvents()
this.layoutTabs()
this.fixZIndexes()
this.setupDraggabilly()
}
emit(eventName, data) {
this.el.dispatchEvent(new CustomEvent(eventName, { detail: data }))
}
setupStyleEl() {
this.animationStyleEl = document.createElement('style')
this.el.appendChild(this.animationStyleEl)
}
setupEvents() {
window.addEventListener('resize', event => this.layoutTabs())
document.body.querySelector('#chrome-tabs-add-tab').addEventListener('click', event => this.emit('requestNewTab'))
this.el.addEventListener('click', ({target}) => {
if (target.classList.contains('chrome-tab')) {
this.setCurrentTab(target)
} else if (target.classList.contains('chrome-tab-close')) {
this.emit('requestTabClose', {tabEl: target.parentNode})
} else if (target.classList.contains('chrome-tab-title') || target.classList.contains('chrome-tab-favicon')) {
this.setCurrentTab(target.parentNode)
}
})
}
get tabEls() {
return Array.prototype.slice.call(this.el.querySelectorAll('.chrome-tab'))
}
get tabContentEl() {
return this.el.querySelector('.chrome-tabs-content')
}
get tabWidth() {
const tabsContentWidth = this.tabContentEl.clientWidth - this.options.tabOverlapDistance
const width = (tabsContentWidth / this.tabEls.length) + this.options.tabOverlapDistance
return Math.max(this.options.minWidth, Math.min(this.options.maxWidth, width))
}
get tabEffectiveWidth() {
return this.tabWidth - this.options.tabOverlapDistance
}
get tabPositions() {
const tabEffectiveWidth = this.tabEffectiveWidth
let left = 0
let positions = []
this.tabEls.forEach((tabEl, i) => {
positions.push(left)
left += tabEffectiveWidth
})
return positions
}
layoutTabs() {
const tabWidth = this.tabWidth
this.cleanUpPreviouslyDraggedTabs()
this.tabEls.forEach((tabEl) => tabEl.style.width = tabWidth + 'px')
requestAnimationFrame(() => {
let styleHTML = ''
this.tabPositions.forEach((left, i) => {
styleHTML += `
.chrome-tabs[data-chrome-tabs-instance-id="${ this.instanceId }"] .chrome-tab:nth-child(${ i + 1}) {
transform: translate3d(${ left }px, 0, 0)
}
`
})
this.animationStyleEl.innerHTML = styleHTML
})
}
fixZIndexes() {
const bottomBarEl = this.el.querySelector('.chrome-tabs-bottom-bar')
const tabEls = this.tabEls
tabEls.forEach((tabEl, i) => {
let zIndex = tabEls.length - i
if (tabEl.classList.contains('chrome-tab-current')) {
bottomBarEl.style.zIndex = tabEls.length + 1
zIndex = tabEls.length + 2
}
tabEl.style.zIndex = zIndex
})
}
createNewTabEl() {
const div = document.createElement('div')
div.innerHTML = tabTemplate
return div.firstElementChild
}
addTab(tabProperties) {
const tabEl = this.createNewTabEl()
tabEl.setAttribute('data-tab-id', tabProperties.id)
tabEl.classList.add('chrome-tab-just-added')
setTimeout(() => tabEl.classList.remove('chrome-tab-just-added'), 500)
tabProperties = Object.assign({}, defaultTabProperties, tabProperties)
this.tabContentEl.appendChild(tabEl)
this.updateTab(tabEl, tabProperties)
this.emit('tabAdd', { tabEl })
this.setCurrentTab(tabEl)
this.layoutTabs()
this.fixZIndexes()
this.setupDraggabilly()
}
setCurrentTab(tabEl) {
const currentTab = this.el.querySelector('.chrome-tab-current')
if (currentTab) currentTab.classList.remove('chrome-tab-current')
tabEl.classList.add('chrome-tab-current')
this.fixZIndexes()
this.emit('activeTabChange', { tabEl })
}
removeTab(tabEl) {
if (tabEl.classList.contains('chrome-tab-current')) {
if (tabEl.previousElementSibling) {
this.setCurrentTab(tabEl.previousElementSibling)
} else if (tabEl.nextElementSibling) {
this.setCurrentTab(tabEl.nextElementSibling)
}
}
tabEl.parentNode.removeChild(tabEl)
this.emit('tabRemove', { tabEl })
this.layoutTabs()
this.fixZIndexes()
this.setupDraggabilly()
}
updateTab(tabEl, tabProperties) {
tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title
tabEl.querySelector('.chrome-tab-favicon').style.backgroundImage = `url('${tabProperties.favicon}')`
tabEl.querySelector('.chrome-tab-favicon').style.display = tabProperties.loading ? 'none' : 'inline-block'
tabEl.querySelector('.chrome-tab-spinner').style.display = tabProperties.loading ? 'inline-block' : 'none'
}
cleanUpPreviouslyDraggedTabs() {
this.tabEls.forEach((tabEl) => tabEl.classList.remove('chrome-tab-just-dragged'))
}
setupDraggabilly() {
const tabEls = this.tabEls
const tabEffectiveWidth = this.tabEffectiveWidth
const tabPositions = this.tabPositions
this.draggabillyInstances.forEach(draggabillyInstance => draggabillyInstance.destroy())
tabEls.forEach((tabEl, originalIndex) => {
const originalTabPositionX = tabPositions[originalIndex]
const draggabillyInstance = new Draggabilly(tabEl, {
axis: 'x',
containment: this.tabContentEl
})
this.draggabillyInstances.push(draggabillyInstance)
draggabillyInstance.on('dragStart', () => {
this.cleanUpPreviouslyDraggedTabs()
tabEl.classList.add('chrome-tab-currently-dragged')
this.el.classList.add('chrome-tabs-sorting')
this.fixZIndexes()
})
draggabillyInstance.on('dragEnd', () => {
const finalTranslateX = parseFloat(tabEl.style.left, 10)
tabEl.style.transform = `translate3d(0, 0, 0)`
// Animate dragged tab back into its place
requestAnimationFrame(() => {
tabEl.style.left = '0'
tabEl.style.transform = `translate3d(${ finalTranslateX }px, 0, 0)`
requestAnimationFrame(() => {
tabEl.classList.remove('chrome-tab-currently-dragged')
this.el.classList.remove('chrome-tabs-sorting')
this.setCurrentTab(tabEl)
tabEl.classList.add('chrome-tab-just-dragged')
requestAnimationFrame(() => {
tabEl.style.transform = ''
this.setupDraggabilly()
})
})
})
})
draggabillyInstance.on('dragMove', (event, pointer, moveVector) => {
// Current index be computed within the event since it can change during the dragMove
const tabEls = this.tabEls
const currentIndex = tabEls.indexOf(tabEl)
const currentTabPositionX = originalTabPositionX + moveVector.x
const destinationIndex = Math.max(0, Math.min(tabEls.length, Math.floor((currentTabPositionX + (tabEffectiveWidth / 2)) / tabEffectiveWidth)))
if (currentIndex !== destinationIndex) {
this.animateTabMove(tabEl, currentIndex, destinationIndex)
}
})
})
}
animateTabMove(tabEl, originIndex, destinationIndex) {
if (destinationIndex < originIndex) {
tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex])
} else {
tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex + 1])
}
}
}
if (isNodeContext) {
module.exports = ChromeTabs
} else {
window.ChromeTabs = ChromeTabs
}
})()
File diff suppressed because one or more lines are too long
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="48.625px" height="48.625px" viewBox="0 0 48.625 48.625" style="enable-background:new 0 0 48.625 48.625; fill:#908da2;"
xml:space="preserve">
<g>
<g>
<polygon points="35.432,10.815 35.479,11.176 34.938,11.288 34.866,12.057 35.514,12.057 36.376,11.974 36.821,11.445
36.348,11.261 36.089,10.963 35.7,10.333 35.514,9.442 34.783,9.591 34.578,9.905 34.578,10.259 34.93,10.5 "/>
<polygon points="34.809,11.111 34.848,10.629 34.419,10.444 33.819,10.583 33.374,11.297 33.374,11.76 33.893,11.76 "/>
<path d="M22.459,13.158l-0.132,0.34h-0.639v0.33h0.152c0,0,0.009,0.07,0.022,0.162l0.392-0.033l0.245-0.152l0.064-0.307
l0.317-0.027l0.125-0.258l-0.291-0.06L22.459,13.158z"/>
<polygon points="20.812,13.757 20.787,14.08 21.25,14.041 21.298,13.717 21.02,13.498 "/>
<path d="M48.619,24.061c-0.007-0.711-0.043-1.417-0.11-2.112c-0.225-2.317-0.779-4.538-1.609-6.62
c-0.062-0.155-0.119-0.312-0.185-0.465c-1.106-2.613-2.659-4.992-4.56-7.045c-0.125-0.134-0.252-0.266-0.379-0.396
c-0.359-0.373-0.728-0.737-1.11-1.086C36.344,2.402,30.604,0,24.312,0C17.967,0,12.186,2.445,7.852,6.44
C6.842,7.371,5.914,8.387,5.072,9.475C1.896,13.583,0,18.729,0,24.312c0,13.407,10.907,24.313,24.313,24.313
c9.43,0,17.617-5.4,21.647-13.268c0.862-1.682,1.533-3.475,1.985-5.354c0.115-0.477,0.214-0.956,0.3-1.441
c0.245-1.381,0.379-2.801,0.379-4.25C48.625,24.228,48.62,24.145,48.619,24.061z M44.043,14.344l0.141-0.158
c0.185,0.359,0.358,0.724,0.523,1.094l-0.23-0.009l-0.434,0.06V14.344z M40.53,10.102l0.004-1.086
c0.382,0.405,0.75,0.822,1.102,1.254l-0.438,0.652l-1.531-0.014l-0.096-0.319L40.53,10.102z M11.202,7.403V7.362h0.487
l0.042-0.167h0.797v0.348l-0.229,0.306h-1.098L11.202,7.403L11.202,7.403z M11.98,8.488c0,0,0.487-0.083,0.529-0.083
s0,0.486,0,0.486L11.411,8.96l-0.209-0.25L11.98,8.488z M45.592,18.139h-1.779l-1.084-0.807l-1.141,0.111v0.696h-0.361
l-0.39-0.278l-1.976-0.501v-1.28l-2.504,0.195l-0.776,0.417h-0.994L34.1,16.643l-1.207,0.67v1.261l-2.467,1.78l0.205,0.76h0.5
L31,21.838l-0.352,0.129l-0.019,1.892l2.132,2.428h0.928l0.056-0.148h1.668l0.481-0.445h0.946l0.519,0.52l1.41,0.146l-0.187,1.875
l1.565,2.763l-0.824,1.575l0.056,0.742l0.649,0.647v1.784l0.852,1.146v1.482h0.736c-4.096,5.029-10.33,8.25-17.305,8.25
C12.009,46.625,2,36.615,2,24.312c0-3.097,0.636-6.049,1.781-8.732v-0.696l0.798-0.969c0.277-0.523,0.574-1.033,0.891-1.53
l0.036,0.405l-0.926,1.125c-0.287,0.542-0.555,1.096-0.798,1.665v1.27l0.927,0.446v1.765l0.889,1.517l0.723,0.111l0.093-0.52
l-0.853-1.316l-0.167-1.279h0.5l0.211,1.316l1.233,1.799L7.02,21.27l0.784,1.199l1.947,0.482v-0.315l0.779,0.111l-0.074,0.556
l0.612,0.112l0.945,0.258l1.335,1.521l1.705,0.129l0.167,1.391l-1.167,0.816l-0.055,1.242l-0.167,0.76l1.688,2.113l0.129,0.724
c0,0,0.612,0.166,0.687,0.166c0.074,0,1.372,0.983,1.372,0.983v3.819l0.463,0.13l-0.315,1.762l0.779,1.039l-0.144,1.746
l1.029,1.809l1.321,1.154l1.328,0.024l0.13-0.427l-0.976-0.822l0.056-0.408l0.175-0.5l0.037-0.51l-0.66-0.02l-0.333-0.418
l0.548-0.527l0.074-0.398l-0.612-0.175l0.036-0.37l0.872-0.132l1.326-0.637l0.445-0.816l1.391-1.78l-0.316-1.392l0.427-0.741
l1.279,0.039l0.861-0.682l0.278-2.686l0.955-1.213l0.167-0.779l-0.871-0.279l-0.575-0.943l-1.965-0.02l-1.558-0.594l-0.074-1.111
l-0.52-0.909l-1.409-0.021l-0.814-1.278l-0.723-0.353l-0.037,0.39l-1.316,0.078l-0.482-0.671l-1.373-0.279l-1.131,1.307
l-1.78-0.302l-0.129-2.006l-1.299-0.222l0.521-0.984l-0.149-0.565l-1.707,1.141l-1.074-0.131L9.48,21.016l0.234-0.865l0.592-1.091
l1.363-0.69l2.632-0.001l-0.007,0.803l0.946,0.44l-0.075-1.372l0.682-0.686l1.376-0.904l0.094-0.636l1.372-1.428l1.459-0.808
l-0.129-0.106l0.988-0.93l0.362,0.096l0.166,0.208l0.375-0.416l0.092-0.041l-0.411-0.058l-0.417-0.139v-0.4l0.221-0.181h0.487
l0.223,0.098l0.193,0.39l0.236-0.036v-0.034l0.068,0.023l0.684-0.105l0.097-0.334l0.39,0.098v0.362l-0.362,0.249h0.001
l0.053,0.397l1.239,0.382c0,0,0.001,0.005,0.003,0.015l0.285-0.024l0.019-0.537l-0.982-0.447l-0.056-0.258l0.815-0.278l0.036-0.78
l-0.852-0.519l-0.056-1.315l-1.168,0.574h-0.426l0.112-1.001l-1.59-0.375l-0.658,0.497v1.516l-1.183,0.375l-0.474,0.988
l-0.514,0.083v-1.264l-1.112-0.154l-0.556-0.362l-0.224-0.819l1.989-1.164l0.973-0.296l0.098,0.654l0.542-0.028l0.042-0.329
l0.567-0.081l0.01-0.115l-0.244-0.101l-0.056-0.348l0.697-0.059l0.421-0.438l0.023-0.032l0.005,0.002l0.128-0.132l1.465-0.185
l0.648,0.55l-1.699,0.905l2.162,0.51l0.28-0.723h0.945l0.334-0.63l-0.668-0.167V6.212L22.69,5.284l-1.446,0.167l-0.816,0.427
l0.056,1.038l-0.853-0.13L19.5,6.212l0.817-0.742l-1.483-0.074l-0.426,0.129l-0.185,0.5l0.556,0.094l-0.111,0.556l-0.945,0.056
l-0.148,0.37l-1.371,0.038c0,0-0.038-0.778-0.093-0.778c-0.055,0,1.075-0.019,1.075-0.019l0.817-0.798l-0.446-0.223l-0.593,0.576
l-0.984-0.056l-0.593-0.816h-1.261L12.81,6.008h1.206l0.11,0.353l-0.313,0.291l1.335,0.037l0.204,0.482l-1.503-0.056l-0.073-0.371
L12.831,6.54L12.33,6.262l-1.125,0.009C14.888,3.588,19.417,2,24.312,2c5.642,0,10.797,2.109,14.73,5.574l-0.265,0.474
l-1.029,0.403l-0.434,0.471l0.1,0.549l0.531,0.074l0.32,0.8l0.916-0.369l0.151,1.07h-0.276l-0.752-0.111l-0.834,0.14l-0.807,1.14
l-1.154,0.181l-0.167,0.988l0.487,0.115l-0.141,0.635l-1.146-0.23l-1.051,0.23l-0.223,0.585l0.182,1.228l0.617,0.289l1.035-0.006
l0.699-0.063l0.213-0.556l1.092-1.419l0.719,0.147l0.708-0.64l0.132,0.5l1.742,1.175l-0.213,0.286l-0.785-0.042l0.302,0.428
l0.483,0.106l0.566-0.236l-0.012-0.682l0.251-0.126l-0.202-0.214l-1.162-0.648l-0.306-0.861h0.966l0.309,0.306l0.832,0.717
l0.035,0.867l0.862,0.918l0.321-1.258l0.597-0.326l0.112,1.029l0.583,0.64l1.163-0.02c0.225,0.579,0.427,1.168,0.604,1.769
L45.592,18.139z M13.261,11.046l0.584-0.278l0.528,0.126l-0.182,0.709l-0.57,0.181L13.261,11.046z M16.36,12.715v0.459h-1.334
l-0.5-0.139l0.125-0.32l0.641-0.265h0.876v0.265H16.36z M16.974,13.355V13.8l-0.334,0.215l-0.416,0.077c0,0,0-0.667,0-0.737
H16.974z M16.598,13.174v-0.529l0.459,0.418L16.598,13.174z M16.807,14.244v0.433l-0.319,0.32h-0.709l0.111-0.486l0.335-0.029
l0.069-0.167L16.807,14.244z M15.041,13.355h0.737l-0.945,1.321l-0.39-0.209l0.084-0.556L15.041,13.355z M18.059,14.092v0.432
H17.35l-0.194-0.28v-0.402h0.056L18.059,14.092z M17.404,13.498l0.202-0.212l0.341,0.212l-0.273,0.225L17.404,13.498z
M45.954,19.265l0.07-0.082c0.029,0.126,0.06,0.252,0.088,0.38L45.954,19.265z"/>
<path d="M3.782,14.884v0.696c0.243-0.568,0.511-1.122,0.798-1.665L3.782,14.884z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,135 @@
<html>
<head>
<title>New Tab</title>
<style type="text/css">
body {
background: #16151d;
color: #c4c2d0;
display: flex;
justify-content: center;
align-items: center;
}
* {
font-family: Segoe UI, -apple-system, 'Arial', sans-serif;
}
h1 {
text-align: center;
font-size: 4em;
color: #2d2d3d;
margin: 60px auto;
margin-top: 0;
font-weight: 200;
font-family: 'Segoe UI Light', -apple-system, 'Arial', sans-serif;
position: fixed;
top: 14%;
}
h1 img {
margin-left: -30px;
}
div.row {
clear: both;
width: 652;
margin: 0 auto;
background-color: black;
}
div.row div {
background: red;
}
div.row div a {
width: 144;
height: 144;
border: none;
border-width: 3px;
float: left;
margin: 5px;
border-radius: 50%;
text-decoration: none;
color: #c4c2d0;
font-size: 0.9em;
transition: 0.1s;
background: linear-gradient(225deg, #262633, #1e1e29);
position: relative;
}
div.row div a:active {
margin-top: 10px;
margin-bottom: 0px;
box-shadow: none !important;
}
div.row div a.special {
background: linear-gradient(45deg, #4085ec, #592dd2, #b16ae7);
}
div.row div a.special span {
/*background-color: rgba(225, 220, 255, 0.3); */
color: rgb(225, 220, 244);
}
div.row div a span {
display: block;
height: 1.3em;
/*background-color: #FAFAFA; */
padding: 8px;
border-radius: 8px 8px 0 0;
transition: 0.1s;
position: absolute;
margin: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 140px;
text-align: center;
}
pre {
font-family: Courier New;
color: #888;
width: 610px;
margin: 70 auto;
}
div.row div a:hover {
background: linear-gradient(225deg, #353549, #262633);
color: white;
border-color: #833efa;
/*background: linear-gradient(#E8E8E8, #FFF); */
}
div.row div a:hover span {
/*background-color: #FCFCFC; */
}
div.row div a.special:hover span {
/*background-color: rgba(225, 220, 255, 0.3); */
color: white;
}
div.row div a.special:hover {
background: linear-gradient(45deg, #4e95ff, #6032e4, #c478ff);
}
</style>
</head>
<body>
<h1><img src="logo.png" width="300" /></h1>
<div id="wrap">
<div class="row">
<div><a class="special" href="file:///release_notes.html"><span>Release Notes</span></a></div>
<div><a href="https://en.wikipedia.com"><span>Wikipedia</span></a></div>
<div><a href="https://news.ycombinator.com"><span>Hacker News</span></a></div>
<div><a href="https://google.com/"><span>Google</span></a></div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,247 @@
<html>
<head>
<title>Release Notes</title>
<style type="text/css">
body {
background: linear-gradient(45deg, #4085ec, #592dd2, #b16ae7);
margin: 0;
line-height: 1.5;
font-weight: 300;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
color: #e3e3e3;
}
h1, h2, h3, h4, strong, dt {
font-weight: 500;
color: white;
letter-spacing: 0.01em;
}
h2 {
font-size: 2em;
margin-bottom: 1em;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: 0.5em;
}
a, a:link, a:visited {
color: #ffffffd9;
font-weight: 500;
background: rgba(0, 0, 0, 0.3);
padding: 4px 14px;
border-radius: 18px;
text-decoration: none;
transition: all 0.2s ease;
display: inline-block;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
a:hover {
color: white;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
a:active {
transform: translateY(1px);
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
}
code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
background-color: rgb(0 0 0 / 16%);
padding: 2px 5px;
font-size: 0.9em;
border-radius: 4px;
color: #ffffffdb;
}
#content {
max-width: 800px;
width: 90%;
margin: 60px auto;
padding: 50px;
color: white;
border-radius: 20px;
background: rgba(0, 0, 0, 0.3);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(255, 255, 255, 0.1) inset;
border: 1px solid rgba(0, 0, 0, 0.2);
}
h3 {
margin-top: 1em;
font-size: 1.5em;
}
h4, dt {
margin: 0;
margin-top: 1.5em;
padding: 6px 0;
font-size: 1.2em;
}
p {
padding: 6px 0;
margin: 0.5em 0;
margin-bottom: 1.5em;
line-height: 1.6;
font-size: 1.1em;
}
ul {
padding-left: 1.5em;
}
li {
margin-bottom: 0.5em;
}
dd ul {
padding: 0;
margin: 0.5em 0 1.5em 0;
list-style-type: none;
}
.section-list {
margin-left: 1.2em;
}
.section-list li {
margin-bottom: 0.7em;
}
.signature {
text-align: right;
margin-top: 3em;
font-style: italic;
opacity: 0.8;
color: white;
}
dl {
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 1em 2em;
align-items: center;
}
dt {
margin-top: 0;
}
dd {
margin: 0;
}
</style>
</head>
<body>
<div id="content">
<h2>Ultralight v1.4.0 Release Notes</h2>
<p>This major new release updates the engine to latest WebKit, adds support for macOS Apple Silicon and Linux arm64 architectures,
enables CSS filters (blurs, saturation, etc.), adds API to composite external image sources, and much more.
</p>
<h3>Changelog</h3>
<h4 id="major-improvements">Major Improvements</h4>
<ul>
<li>Synchronize with upstream WebKit (Safari 16.4.1 / WebKit 615.1.18.100.1)</li>
<li>Add support for Apple Silicon architecture on macOS</li>
<li>Add support for ARM64 architecture on Linux</li>
<li>Add support for WebP and animated WebP videos</li>
<li>Add support for compositing external textures / images (new <code>ImageSource</code> API)</li>
<li>Add support for CSS filter effects (blurs, saturation, sepia, etc.) (note: not yet natively GPU accelerated, incoming in 1.4.1)</li>
<li>Add support for file downloads (see <code>View::set_download_listener</code>)</li>
<li>Add support network whitelist / blacklist (see <code>View::set_network_listener</code>)</li>
<li>Add support for variable fonts</li>
<li>Animation and smooth scroll is now driven by <code>Renderer::RefreshDisplay</code></li>
<li>Improve interaction and rendering of dropdown menus</li>
<li>Improve support for SVG / Canvas path rendering</li>
<li>Improve support for SVG / CSS clips and masks</li>
<li>Improve performance of Windows port (now built via Clang)</li>
<li>Improve performance of AppCore (now supports high refresh-rate monitors)</li>
<li>Improve Chinese-Japanese-Korean text rendering on Linux (AppCore)</li>
<li>Improve scroll animation performance</li>
<li>Improve performance of the CPU renderer</li>
</ul>
<h4 id="major-api-changes">Major API Changes</h4>
<h4>New Classes / Structs:</h4>
<ul class="section-list">
<li><code>ImageSource</code></li>
<li><code>ImageSourceProvider</code></li>
<li><code>NetworkListener</code></li>
<li><code>NetworkRequest</code></li>
<li><code>DownloadListener</code></li>
<li><code>ConsoleMessage</code></li>
<li><code>ThreadFactory</code></li>
</ul>
<h4>New API Functions:</h4>
<ul class="section-list">
<li><code>Renderer::RefreshDisplay()</code> (must be called to update animations and scroll)</li>
<li><code>View::set_display_id()</code></li>
<li><code>View::display_id()</code></li>
<li><code>View::set_network_listener()</code></li>
<li><code>View::set_download_listener()</code></li>
<li><code>View::CancelDownload()</code></li>
<li><code>Platform::set_thread_factory()</code></li>
<li><code>String::Hash()</code></li>
<li><code>String</code> (various comparison operators)</li>
<li><code>Monitor::display_id()</code></li>
<li><code>Monitor::refresh_rate()</code></li>
<li><code>Window::EnableFrameStatistics()</code></li>
<li><code>ShowMessageBox()</code></li>
</ul>
<h4>Changes to Existing API:</h4>
<ul class="section-list">
<li><code>ViewListener::OnAddConsoleMessage()</code> has different parameters</li>
<li><code>ViewConfig</code> struct has a new field "<code>display_id</code>"</li>
<li><code>MessageSource</code> enum values have changed</li>
</ul>
<h4 id="major-bugfixes">Major Bugfixes</h4>
<ul>
<li>Fix issue where <code>Config::user_stylesheet</code> would not be applied if HTML doctype was not set</li>
<li>Fix issue with alpha blending and opacity in the CPU renderer</li>
<li>Fix issue tessellating stroked paths with round and square line-caps in the GPU renderer</li>
<li>Fix issue drawing radial gradients in the GPU renderer</li>
<li>Fix issue where CSS dashed borders would only be applied to an element when the border radius was rounded</li>
<li>Fix issue where files would not be displayed consistently across platforms due to inconsistent mime-type mappings</li>
<li>Fix issue validating SSL certificates on Linux and macOS</li>
<li>Fix issue where cookies weren't applied to subdomains</li>
<li>Fix issue where hard reloads weren't sending no-cache headers</li>
<li>Fix issue drawing CSS box shadows outside of the initial View rectangle</li>
<li>Fix issue with flickering artifacts on macOS/Metal when a Window has multiple View overlays assigned to it</li>
<li>Fix issue compiling on older GCC toolchains</li>
<li>Fix crash when rendering certain paths</li>
<li>Fix crash in C API due to improper mappings of <code>ULMessageSource</code> and <code>ULMessageLevel</code> enums</li>
<li>Fix crash when system page size is less than 16384 bytes</li>
<li>Fix crash when switching networks on macOS</li>
<li>Fix crash when a remote inspector backend was closed</li>
<li>Fix crash when a <code>View</code> was destroyed with dropdown menu open</li>
<li>Fix crash when loading Data URIs and certain resource types</li>
<li>Fix memory leaks in the CPU renderer</li>
<li>Fix memory leak in JavaScriptCore</li>
<li>Fix various crashes at shutdown</li>
<li>Fix high CPU usage when a WebSocket connection was active</li>
<li>Mitigate memory growth when drawing many Canvas elements</li>
</ul>
<h3>Useful Links</h3>
<dl>
<dt>Website</dt>
<dd><a href="https://ultralig.ht">ultralig.ht</a></dd>
<dt>Support Docs</dt>
<dd><a href="https://docs.ultralig.ht">docs.ultralig.ht</a></dd>
</dl>
<p class="signature">- Ultralight Team</p>
</div>
</body>
</html>
@@ -0,0 +1,12 @@
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="14px" height="14px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<path fill="#7a768f" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
<animateTransform attributeType="xml"
attributeName="transform"
type="rotate"
from="0 25 25"
to="360 25 25"
dur="0.6s"
repeatCount="indefinite"/>
</path>
</svg>

After

Width:  |  Height:  |  Size: 649 B

@@ -0,0 +1,73 @@
* {
overflow: hidden;
user-select: none;
-webkit-user-select: none;
}
body {
margin: 0;
padding: 0;
font-family: "Segoe UI", -apple-system, Ubuntu, Arial, sans-serif;
font-size: 14px;
background: #16151d;
color: #c4c2d0;
}
#bar {
background: #232330;
background: linear-gradient(0deg, #232330, #282836);
height: 32px;
padding: 4px 9px;
border-bottom: 0.8px solid #252532;
cursor: default;
display: flex;
}
#address {
border-radius: 24px;
border: none;
height: 32px;
flex: 1;
font-size: 14px;
color: #c4c2d0;
background-color: #16151d;
display: inline-block;
padding: 2px 16px 2px 16px;
margin-top: 0px;
margin-left: 9px;
margin-right: 9px;
user-select: auto !important;
-webkit-user-select: text !important;
}
#address:focus {
background-color: #16151d;
color: #FFF;
}
.icon {
cursor: default;
fill: rgba(200, 200, 200, 1.0);
vertical-align: top;
margin-top: -1px;
padding: 5px;
height: 22px;
}
#back {
margin-left: 0px;
}
.icon.disabled {
fill: rgb(99 96 116);
}
svg#icon_defs path {
fill: inherit;
}
.icon:not(.disabled):hover {
background-color: #343446;
border-radius: 100%;
}
@@ -0,0 +1,139 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="ui.css">
<link rel="stylesheet" type="text/css" href="chrome-tabs.css">
<script src="ui.js"></script>
<script src="anchorme.js"></script>
</head>
<body>
<button id="chrome-tabs-add-tab">+ New Tab</button>
<div class="chrome-tabs" data-chrome-tabs-instance-id="0">
<div class="chrome-tabs-content"></div>
<div class="chrome-tabs-bottom-bar" style="z-index: 3;"></div>
</div>
<svg style="display: none;">
<defs>
<g id="svg_arrow_back">
<path d="M427 277v-42h-260l119 -120l-30 -30l-171 171l171 171l30 -30l-119 -120h260z" />
</g>
<g id="svg_arrow_forward">
<path d="M256 427l171 -171l-171 -171l-30 30l119 120h-260v42h260l-119 120z" />
</g>
<g id="svg_refresh">
<path transform="matrix(1 0 0 -1 0 512)"
d="M377 377l50 50v-150h-150l69 69c-23 23 -55 38 -90 38c-71 0 -128 -57 -128 -128s57 -128 128 -128c56 0 104 35 121 85h44c-19 -74 -85 -128 -165 -128c-94 0 -170 77 -170 171s76 171 170 171c47 0 90 -19 121 -50z" />
</g>
<g id="svg_stop">
<path
d="M405 375l-119 -119l119 -119l-30 -30l-119 119l-119 -119l-30 30l119 119l-119 119l30 30l119 -119l119 119z" />
</g>
<g id="svg_tools">
<path
d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z" />
</g>
</defs>
</svg>
<div id="bar">
<span id="back" class="icon disabled">
<svg viewBox="0 0 512 512" width="20" height="20">
<use xlink:href="#svg_arrow_back" />
</svg>
</span>
<span id="forward" class="icon disabled">
<svg viewBox="0 0 512 512" width="20" height="20">
<use xlink:href="#svg_arrow_forward" />
</svg>
</span>
<span id="refresh" class="icon">
<svg viewBox="0 0 512 512" width="20" height="20">
<use xlink:href="#svg_refresh" />
</svg>
</span>
<span id="stop" class="icon" style="display: none;">
<svg viewBox="0 0 512 512" width="20" height="20">
<use xlink:href="#svg_stop" />
</svg>
</span>
<input onfocus="select_next_mouseup = true;"
onmouseup="if (select_next_mouseup) this.select(); select_next_mouseup = false;" type="text"
id="address"></input>
<span id="toggle-tools" class="icon">
<svg viewBox="0 0 24 24" width="18" height="18">
<use xlink:href="#svg_tools" />
</svg>
</span>
</div>
<script src="draggabilly.pkgd.min.js"></script>
<script src="chrome-tabs.js"></script>
<script>
var el = document.querySelector('.chrome-tabs')
var chromeTabs = new ChromeTabs()
var select_next_mouseup = false;
var defaultFavicon = "earth.svg";
chromeTabs.init(el, {
tabOverlapDistance: 6,
minWidth: 45,
maxWidth: 243
})
function addTab(id, title, faviconUrl, isLoading) {
chromeTabs.addTab({ id: id, title: title, favicon: defaultFavicon, loading: isLoading });
}
function updateTab(id, title, faviconUrl, isLoading) {
const tab = document.querySelector("[data-tab-id='" + id + "']");
if (tab)
chromeTabs.updateTab(tab, { title: title, favicon: defaultFavicon, loading: isLoading });
}
function closeTab(id) {
const tab = document.querySelector("[data-tab-id='" + id + "']");
if (tab)
chromeTabs.removeTab(tab);
}
function bindCallbacks() {
el.addEventListener('requestNewTab', ({ detail }) => OnRequestNewTab());
el.addEventListener('requestTabClose', ({ detail }) => OnRequestTabClose(detail.tabEl.getAttribute('data-tab-id')));
el.addEventListener('activeTabChange', ({ detail }) => OnActiveTabChange(detail.tabEl.getAttribute('data-tab-id')));
document.querySelector('#back').addEventListener('click', event => OnBack());
document.querySelector('#forward').addEventListener('click', event => OnForward());
document.querySelector('#refresh').addEventListener('click', event => OnRefresh());
document.querySelector('#stop').addEventListener('click', event => OnStop());
document.querySelector('#toggle-tools').addEventListener('click', event => OnToggleTools());
var address = document.querySelector('#address');
address.onkeypress = (function (e) {
if (e.which == '13') {
address.blur();
let url = address.value;
if (anchorme.validate.url(url) || anchorme.validate.ip(url)) {
if (url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://")) {
OnRequestChangeURL(url);
} else {
OnRequestChangeURL("http://" + url);
}
} else if (url.toLowerCase().startsWith("file:///")) {
OnRequestChangeURL(url);
} else if (url.toLowerCase().startsWith("file://")) {
OnRequestChangeURL("file:///" + url.substring(7));
} else {
// Interpret as search
OnRequestChangeURL("https://www.google.com/search?q=" + encodeURIComponent(url));
}
return false;
}
});
}
bindCallbacks();
</script>
</body>
</html>
@@ -0,0 +1,27 @@
function updateBack(enable) {
if (enable)
document.getElementById("back").classList.remove("disabled");
else
document.getElementById("back").classList.add("disabled");
}
function updateForward(enable) {
if (enable)
document.getElementById("forward").classList.remove("disabled");
else
document.getElementById("forward").classList.add("disabled");
}
function updateLoading(is_loading) {
if (is_loading) {
document.getElementById("refresh").style.display = "none";
document.getElementById("stop").style.display = "inline-block";
} else {
document.getElementById("refresh").style.display = "inline-block";
document.getElementById("stop").style.display = "none";
}
}
function updateURL(url) {
document.getElementById('address').value = url;
}
@@ -0,0 +1,32 @@
#include "Browser.h"
#include <Ultralight/platform/Platform.h>
#include <Ultralight/platform/Config.h>
#include <Ultralight/Renderer.h>
Browser::Browser() {
Settings settings;
Config config;
config.scroll_timer_delay = 1.0 / 90.0;
app_ = App::Create(settings, config);
window_ = Window::Create(app_->main_monitor(), 1024, 768, false,
kWindowFlags_Resizable | kWindowFlags_Titled | kWindowFlags_Maximizable);
window_->SetTitle("Ultralight Sample 8 - Web Browser");
// Create the UI
ui_.reset(new UI(window_));
window_->set_listener(ui_.get());
}
Browser::~Browser() {
window_->set_listener(nullptr);
ui_.reset();
window_ = nullptr;
app_ = nullptr;
}
void Browser::Run() {
app_->Run();
}
@@ -0,0 +1,17 @@
#include <AppCore/AppCore.h>
#include "UI.h"
using namespace ultralight;
class Browser {
public:
Browser();
virtual ~Browser();
virtual void Run();
protected:
RefPtr<App> app_;
RefPtr<Window> window_;
std::unique_ptr<UI> ui_;
};
+181
View File
@@ -0,0 +1,181 @@
#include "Tab.h"
#include "UI.h"
#include <iostream>
#include <string>
#define INSPECTOR_DRAG_HANDLE_HEIGHT 10
Tab::Tab(UI* ui, uint64_t id, uint32_t width, uint32_t height, int x, int y)
: ui_(ui), id_(id), container_width_(width), container_height_(height) {
overlay_ = Overlay::Create(ui->window_, width, height, x, y);
view()->set_view_listener(this);
view()->set_load_listener(this);
}
Tab::~Tab() {
view()->set_view_listener(nullptr);
view()->set_load_listener(nullptr);
}
void Tab::Show() {
overlay_->Show();
overlay_->Focus();
if (inspector_overlay_)
inspector_overlay_->Show();
}
void Tab::Hide() {
overlay_->Hide();
overlay_->Unfocus();
if (inspector_overlay_)
inspector_overlay_->Hide();
}
void Tab::ToggleInspector() {
if (!inspector_overlay_) {
view()->CreateLocalInspectorView();
} else {
if (inspector_overlay_->is_hidden()) {
inspector_overlay_->Show();
} else {
inspector_overlay_->Hide();
}
}
// Force resize to update layout
}
bool Tab::IsInspectorShowing() const {
if (!inspector_overlay_)
return false;
return !inspector_overlay_->is_hidden();
}
IntRect Tab::GetInspectorResizeDragHandle() const {
if (!IsInspectorShowing())
return IntRect::MakeEmpty();
int drag_handle_height_px = (uint32_t)std::round(INSPECTOR_DRAG_HANDLE_HEIGHT * ui_->window()->scale());
// This drag handle should span the width of the UI and be centered vertically at the boundary between
// the page overlay and inspector overlay.
int drag_handle_x = (int)inspector_overlay_->x();
int drag_handle_y = (int)inspector_overlay_->y() - drag_handle_height_px / 2;
return { drag_handle_x, drag_handle_y, drag_handle_x + (int)inspector_overlay_->width(),
drag_handle_y + drag_handle_height_px };
}
int Tab::GetInspectorHeight() const {
if (inspector_overlay_)
return inspector_overlay_->height();
return 0;
}
void Tab::SetInspectorHeight(int height) {
if (height > 2) {
inspector_overlay_->Resize(inspector_overlay_->width(), height);
// Trigger a resize to perform re-layout / re-size of content overlay
Resize(container_width_, container_height_);
}
}
void Tab::Resize(uint32_t width, uint32_t height) {
container_width_ = width;
container_height_ = height;
uint32_t content_height = container_height_;
if (inspector_overlay_ && !inspector_overlay_->is_hidden()) {
content_height -= inspector_overlay_->height();
}
if (content_height < 1)
content_height = 1;
overlay_->Resize(container_width_, content_height);
if (inspector_overlay_ && !inspector_overlay_->is_hidden()) {
inspector_overlay_->MoveTo(0, overlay_->y() + overlay_->height());
inspector_overlay_->Resize(container_width_, inspector_overlay_->height());
}
}
void Tab::OnChangeTitle(View* caller, const String& title) {
ui_->UpdateTabTitle(id_, title);
}
void Tab::OnChangeURL(View* caller, const String& url) {
ui_->UpdateTabURL(id_, url);
}
void Tab::OnChangeTooltip(View* caller, const String& tooltip) {}
void Tab::OnChangeCursor(View* caller, Cursor cursor) {
if (id_ == ui_->active_tab_id_)
ui_->SetCursor(cursor);
}
void Tab::OnAddConsoleMessage(View* caller, const ConsoleMessage& msg) {
}
RefPtr<View> Tab::OnCreateChildView(ultralight::View* caller,
const String& opener_url, const String& target_url,
bool is_popup, const IntRect& popup_rect) {
return ui_->CreateNewTabForChildView(target_url);
}
RefPtr<View> Tab::OnCreateInspectorView(ultralight::View* caller, bool is_local,
const String& inspected_url) {
if (inspector_overlay_)
return nullptr;
inspector_overlay_ = Overlay::Create(ui_->window_, container_width_, container_height_ / 2, 0, 0);
// Force resize to update layout
Resize(container_width_, container_height_);
inspector_overlay_->Show();
return inspector_overlay_->view();
}
void Tab::OnBeginLoading(View* caller, uint64_t frame_id, bool is_main_frame, const String& url) {
ui_->UpdateTabNavigation(id_, caller->is_loading(), caller->CanGoBack(), caller->CanGoForward());
}
void Tab::OnFinishLoading(View* caller, uint64_t frame_id, bool is_main_frame, const String& url) {
ui_->UpdateTabNavigation(id_, caller->is_loading(), caller->CanGoBack(), caller->CanGoForward());
}
void Tab::OnFailLoading(View* caller, uint64_t frame_id, bool is_main_frame, const String& url,
const String& description, const String& error_domain, int error_code) {
if (is_main_frame) {
char error_code_str[16];
sprintf(error_code_str,"%d", error_code);
String html_string = "<html><head><style>";
html_string += "* { font-family: sans-serif; }";
html_string += "body { background-color: #CCC; color: #555; padding: 4em; }";
html_string += "dt { font-weight: bold; padding: 1em; }";
html_string += "</style></head><body>";
html_string += "<h2>A Network Error was Encountered</h2>";
html_string += "<dl>";
html_string += "<dt>URL</dt><dd>" + url + "</dd>";
html_string += "<dt>Description</dt><dd>" + description + "</dd>";
html_string += "<dt>Error Domain</dt><dd>" + error_domain + "</dd>";
html_string += "<dt>Error Code</dt><dd>" + String(error_code_str) + "</dd>";
html_string += "</dl></body></html>";
view()->LoadHTML(html_string);
}
}
void Tab::OnUpdateHistory(View* caller) {
ui_->UpdateTabNavigation(id_, caller->is_loading(), caller->CanGoBack(), caller->CanGoForward());
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include <AppCore/AppCore.h>
#include <Ultralight/Listener.h>
class UI;
using namespace ultralight;
/**
* Browser Tab UI implementation. Renders the actual page content in bottom pane.
*/
class Tab : public ViewListener,
public LoadListener {
public:
Tab(UI* ui, uint64_t id, uint32_t width, uint32_t height, int x, int y);
~Tab();
void set_ready_to_close(bool ready) { ready_to_close_ = ready; }
bool ready_to_close() { return ready_to_close_; }
RefPtr<View> view() { return overlay_->view(); }
void Show();
void Hide();
void ToggleInspector();
bool IsInspectorShowing() const;
IntRect GetInspectorResizeDragHandle() const;
int GetInspectorHeight() const;
void SetInspectorHeight(int height);
void Resize(uint32_t width, uint32_t height);
// Inherited from Listener::View
virtual void OnChangeTitle(View* caller, const String& title) override;
virtual void OnChangeURL(View* caller, const String& url) override;
virtual void OnChangeTooltip(View* caller, const String& tooltip) override;
virtual void OnChangeCursor(View* caller, Cursor cursor) override;
virtual void OnAddConsoleMessage(View* caller, const ConsoleMessage& msg) override;
virtual RefPtr<View> OnCreateChildView(ultralight::View* caller,
const String& opener_url, const String& target_url,
bool is_popup, const IntRect& popup_rect) override;
virtual RefPtr<View> OnCreateInspectorView(ultralight::View* caller, bool is_local,
const String& inspected_url) override;
// Inherited from Listener::Load
virtual void OnBeginLoading(View* caller, uint64_t frame_id,
bool is_main_frame, const String& url) override;
virtual void OnFinishLoading(View* caller, uint64_t frame_id,
bool is_main_frame, const String& url) override;
virtual void OnFailLoading(View* caller, uint64_t frame_id,
bool is_main_frame, const String& url, const String& description,
const String& error_domain, int error_code) override;
virtual void OnUpdateHistory(View* caller) override;
protected:
UI* ui_;
RefPtr<Overlay> overlay_;
RefPtr<Overlay> inspector_overlay_;
uint64_t id_;
bool ready_to_close_ = false;
uint32_t container_width_, container_height_;
};
+284
View File
@@ -0,0 +1,284 @@
#include "UI.h"
static UI* g_ui = 0;
#define UI_HEIGHT 80
UI::UI(RefPtr<Window> window) : window_(window), cur_cursor_(Cursor::kCursor_Pointer),
is_resizing_inspector_(false), is_over_inspector_resize_drag_handle_(false) {
uint32_t window_width = window_->width();
ui_height_ = (uint32_t)std::round(UI_HEIGHT * window_->scale());
overlay_ = Overlay::Create(window_, window_width, ui_height_, 0, 0);
g_ui = this;
view()->set_load_listener(this);
view()->set_view_listener(this);
view()->LoadURL("file:///ui.html");
}
UI::~UI() {
view()->set_load_listener(nullptr);
view()->set_view_listener(nullptr);
g_ui = nullptr;
}
bool UI::OnKeyEvent(const ultralight::KeyEvent& evt) {
return true;
}
bool UI::OnMouseEvent(const ultralight::MouseEvent& evt) {
if (active_tab() && active_tab()->IsInspectorShowing()) {
float x_px = std::round(evt.x * window()->scale());
float y_px = std::round(evt.y * window()->scale());
if (is_resizing_inspector_) {
int resize_delta = inspector_resize_begin_mouse_y_ - y_px;
int new_inspector_height = inspector_resize_begin_height_ + resize_delta;
active_tab()->SetInspectorHeight(new_inspector_height);
if (evt.type == MouseEvent::kType_MouseUp) {
is_resizing_inspector_ = false;
}
return false;
}
IntRect drag_handle = active_tab()->GetInspectorResizeDragHandle();
bool over_drag_handle = drag_handle.Contains(Point(x_px, y_px));
if (over_drag_handle && !is_over_inspector_resize_drag_handle_) {
// We entered the drag area
window()->SetCursor(Cursor::kCursor_NorthSouthResize);
is_over_inspector_resize_drag_handle_ = true;
} else if (!over_drag_handle && is_over_inspector_resize_drag_handle_) {
// We left the drag area, restore previous cursor
window()->SetCursor(cur_cursor_);
is_over_inspector_resize_drag_handle_ = false;
}
if (over_drag_handle && evt.type == MouseEvent::kType_MouseDown && !is_resizing_inspector_) {
is_resizing_inspector_ = true;
inspector_resize_begin_mouse_y_ = y_px;
inspector_resize_begin_height_ = active_tab()->GetInspectorHeight();
}
return !over_drag_handle;
}
return true;
}
void UI::OnClose(ultralight::Window* window) {
App::instance()->Quit();
}
void UI::OnResize(ultralight::Window* window, uint32_t width, uint32_t height) {
int tab_height = window->height() - ui_height_;
if (tab_height < 1)
tab_height = 1;
overlay_->Resize(window->width(), ui_height_);
for (auto& tab : tabs_) {
if (tab.second)
tab.second->Resize(window->width(), (uint32_t)tab_height);
}
}
void UI::OnDOMReady(View* caller, uint64_t frame_id, bool is_main_frame, const String& url) {
// Set the context for all subsequent JS* calls
RefPtr<JSContext> locked_context = view()->LockJSContext();
SetJSContext(locked_context->ctx());
JSObject global = JSGlobalObject();
updateBack = global["updateBack"];
updateForward = global["updateForward"];
updateLoading = global["updateLoading"];
updateURL = global["updateURL"];
addTab = global["addTab"];
updateTab = global["updateTab"];
closeTab = global["closeTab"];
global["OnBack"] = BindJSCallback(&UI::OnBack);
global["OnForward"] = BindJSCallback(&UI::OnForward);
global["OnRefresh"] = BindJSCallback(&UI::OnRefresh);
global["OnStop"] = BindJSCallback(&UI::OnStop);
global["OnToggleTools"] = BindJSCallback(&UI::OnToggleTools);
global["OnRequestNewTab"] = BindJSCallback(&UI::OnRequestNewTab);
global["OnRequestTabClose"] = BindJSCallback(&UI::OnRequestTabClose);
global["OnActiveTabChange"] = BindJSCallback(&UI::OnActiveTabChange);
global["OnRequestChangeURL"] = BindJSCallback(&UI::OnRequestChangeURL);
CreateNewTab();
}
void UI::OnBack(const JSObject& obj, const JSArgs& args) {
if (active_tab())
active_tab()->view()->GoBack();
}
void UI::OnForward(const JSObject& obj, const JSArgs& args) {
if (active_tab())
active_tab()->view()->GoForward();
}
void UI::OnRefresh(const JSObject& obj, const JSArgs& args) {
if (active_tab())
active_tab()->view()->Reload();
}
void UI::OnStop(const JSObject& obj, const JSArgs& args) {
if (active_tab())
active_tab()->view()->Stop();
}
void UI::OnToggleTools(const JSObject& obj, const JSArgs& args) {
if (active_tab())
active_tab()->ToggleInspector();
}
void UI::OnRequestNewTab(const JSObject& obj, const JSArgs& args) {
CreateNewTab();
}
void UI::OnRequestTabClose(const JSObject& obj, const JSArgs& args) {
if (args.size() == 1) {
uint64_t id = args[0];
auto& tab = tabs_[id];
if (!tab)
return;
if (tabs_.size() == 1 && App::instance())
App::instance()->Quit();
if (id != active_tab_id_) {
tabs_[id].reset();
tabs_.erase(id);
}
else {
tab->set_ready_to_close(true);
}
RefPtr<JSContext> lock(view()->LockJSContext());
closeTab({ id });
}
}
void UI::OnActiveTabChange(const JSObject& obj, const JSArgs& args) {
if (args.size() == 1) {
uint64_t id = args[0];
if (id == active_tab_id_)
return;
auto& tab = tabs_[id];
if (!tab)
return;
tabs_[active_tab_id_]->Hide();
if (tabs_[active_tab_id_]->ready_to_close()) {
tabs_[active_tab_id_].reset();
tabs_.erase(active_tab_id_);
}
active_tab_id_ = id;
tabs_[active_tab_id_]->Show();
auto tab_view = tabs_[active_tab_id_]->view();
SetLoading(tab_view->is_loading());
SetCanGoBack(tab_view->CanGoBack());
SetCanGoForward(tab_view->CanGoBack());
SetURL(tab_view->url());
}
}
void UI::OnRequestChangeURL(const JSObject& obj, const JSArgs& args) {
if (args.size() == 1) {
ultralight::String url = args[0];
if (!tabs_.empty()) {
auto& tab = tabs_[active_tab_id_];
tab->view()->LoadURL(url);
}
}
}
void UI::CreateNewTab() {
uint64_t id = tab_id_counter_++;
RefPtr<Window> window = window_;
int tab_height = window->height() - ui_height_;
if (tab_height < 1)
tab_height = 1;
tabs_[id].reset(new Tab(this, id, window->width(), (uint32_t)tab_height, 0, ui_height_));
tabs_[id]->view()->LoadURL("file:///new_tab_page.html");
RefPtr<JSContext> lock(view()->LockJSContext());
addTab({ id, "New Tab", "", tabs_[id]->view()->is_loading() });
}
RefPtr<View> UI::CreateNewTabForChildView(const String& url) {
uint64_t id = tab_id_counter_++;
RefPtr<Window> window = window_;
int tab_height = window->height() - ui_height_;
if (tab_height < 1)
tab_height = 1;
tabs_[id].reset(new Tab(this, id, window->width(), (uint32_t)tab_height, 0, ui_height_));
RefPtr<JSContext> lock(view()->LockJSContext());
addTab({ id, "", url, tabs_[id]->view()->is_loading() });
return tabs_[id]->view();
}
void UI::UpdateTabTitle(uint64_t id, const ultralight::String& title) {
RefPtr<JSContext> lock(view()->LockJSContext());
updateTab({ id, title, "", tabs_[id]->view()->is_loading() });
}
void UI::UpdateTabURL(uint64_t id, const ultralight::String& url) {
if (id == active_tab_id_ && !tabs_.empty())
SetURL(url);
}
void UI::UpdateTabNavigation(uint64_t id, bool is_loading, bool can_go_back, bool can_go_forward) {
if (tabs_.empty())
return;
RefPtr<JSContext> lock(view()->LockJSContext());
updateTab({ id, tabs_[id]->view()->title(), "", tabs_[id]->view()->is_loading() });
if (id == active_tab_id_) {
SetLoading(is_loading);
SetCanGoBack(can_go_back);
SetCanGoForward(can_go_forward);
}
}
void UI::SetLoading(bool is_loading) {
RefPtr<JSContext> lock(view()->LockJSContext());
updateLoading({ is_loading });
}
void UI::SetCanGoBack(bool can_go_back) {
RefPtr<JSContext> lock(view()->LockJSContext());
updateBack({ can_go_back });
}
void UI::SetCanGoForward(bool can_go_forward) {
RefPtr<JSContext> lock(view()->LockJSContext());
updateForward({ can_go_forward });
}
void UI::SetURL(const ultralight::String& url) {
RefPtr<JSContext> lock(view()->LockJSContext());
updateURL({ url });
}
void UI::SetCursor(ultralight::Cursor cursor) {
if (App::instance())
window_->SetCursor(cursor);
}
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <AppCore/AppCore.h>
#include "Tab.h"
#include <map>
#include <memory>
using ultralight::JSObject;
using ultralight::JSArgs;
using ultralight::JSFunction;
using namespace ultralight;
class Console;
/**
* Browser UI implementation. Renders the toolbar/addressbar/tabs in top pane.
*/
class UI : public WindowListener,
public LoadListener,
public ViewListener {
public:
UI(RefPtr<Window> window);
~UI();
// Inherited from WindowListener
virtual bool OnKeyEvent(const ultralight::KeyEvent& evt) override;
virtual bool OnMouseEvent(const ultralight::MouseEvent& evt) override;
virtual void OnClose(ultralight::Window* window) override;
virtual void OnResize(ultralight::Window* window, uint32_t width, uint32_t height) override;
// Inherited from LoadListener
virtual void OnDOMReady(View* caller, uint64_t frame_id,
bool is_main_frame, const String& url) override;
// Inherited from ViewListener
virtual void OnChangeCursor(ultralight::View* caller, Cursor cursor) override { SetCursor(cursor); }
// Called by UI JavaScript
void OnBack(const JSObject& obj, const JSArgs& args);
void OnForward(const JSObject& obj, const JSArgs& args);
void OnRefresh(const JSObject& obj, const JSArgs& args);
void OnStop(const JSObject& obj, const JSArgs& args);
void OnToggleTools(const JSObject& obj, const JSArgs& args);
void OnRequestNewTab(const JSObject& obj, const JSArgs& args);
void OnRequestTabClose(const JSObject& obj, const JSArgs& args);
void OnActiveTabChange(const JSObject& obj, const JSArgs& args);
void OnRequestChangeURL(const JSObject& obj, const JSArgs& args);
RefPtr<Window> window() { return window_; }
protected:
void CreateNewTab();
RefPtr<View> CreateNewTabForChildView(const String& url);
void UpdateTabTitle(uint64_t id, const String& title);
void UpdateTabURL(uint64_t id, const String& url);
void UpdateTabNavigation(uint64_t id, bool is_loading, bool can_go_back, bool can_go_forward);
void SetLoading(bool is_loading);
void SetCanGoBack(bool can_go_back);
void SetCanGoForward(bool can_go_forward);
void SetURL(const String& url);
void SetCursor(Cursor cursor);
Tab* active_tab() { return tabs_.empty() ? nullptr : tabs_[active_tab_id_].get(); }
RefPtr<View> view() { return overlay_->view(); }
RefPtr<Window> window_;
RefPtr<Overlay> overlay_;
int ui_height_;
int tab_height_;
float scale_;
std::map<uint64_t, std::unique_ptr<Tab>> tabs_;
uint64_t active_tab_id_ = 0;
uint64_t tab_id_counter_ = 0;
Cursor cur_cursor_;
bool is_resizing_inspector_;
bool is_over_inspector_resize_drag_handle_;
int inspector_resize_begin_height_;
int inspector_resize_begin_mouse_y_;
JSFunction updateBack;
JSFunction updateForward;
JSFunction updateLoading;
JSFunction updateURL;
JSFunction addTab;
JSFunction updateTab;
JSFunction closeTab;
friend class Tab;
};
@@ -0,0 +1,18 @@
#include "Browser.h"
#define ENABLE_PAUSE_FOR_DEBUGGER 0
#if defined(_WIN32) && ENABLE_PAUSE_FOR_DEBUGGER
#include <Windows.h>
void PauseForDebugger() { MessageBoxA(NULL, "Pause", "Caption", MB_OKCANCEL); }
#else
void PauseForDebugger() { }
#endif
int main() {
PauseForDebugger();
Browser browser;
browser.Run();
return 0;
}